Variáveis ​​como $ 0 e $ 1 shell / variáveis ​​de ambiente?

17

Há variáveis do shell como $0, $1, $2, $?, etc.

Tentei imprimir as variáveis ​​de ambiente e shell usando o seguinte comando:

set

Mas essas variáveis ​​não estavam na lista.

Então, basicamente, essas variáveis ​​não são consideradas variáveis ​​de shell / ambiente, certo? (mesmo que para produzi-las, você deve preceder com a $, como você faz com variáveis ​​de shell / ambiente)

user7681202
fonte
3
Os parâmetros posicionais não são variáveis. Você não pode se export 3transformar $3em uma variável de ambiente. Você não pode unset 3; e você não pode atribuir $3um novo valor usando 3=val.
Kaz

Respostas:

25

Variáveis ​​são uma das três variedades distintas de parâmetros no shell.

  1. Uma variável é um parâmetro cujo nome é um identificador de shell válido; começa com _ou uma letra, seguida por zero ou mais letras, números ou _.
  2. Os posicionais parâmetros são os parâmetros numerados $1, $2...
  3. Todos os parâmetros especiais têm nomes de caracteres únicos e, além de $0, são todos vários caracteres de pontuação.

set exibe apenas as variáveis ​​do shell.

Um subconjunto das variáveis ​​do shell são as variáveis ​​de ambiente, cujos valores são herdados do ambiente quando o shell é inicializado ou são criados configurando o exportatributo com um nome válido.

chepner
fonte
1
Observe que setexibe todos os parâmetros em zsh(não $ 1, $ 2 ... mas $ *, $ @) e as funções em bash e bosh. Alguns shells, como o ksh93 e versões mais antigas dos envios de saída do traço, não foram mapeados para variáveis ​​do shell. ( env 1=foo ksh -c setImprimiria 1=foo)
Stéphane Chazelas
11

Variáveis ​​de ambiente versus parâmetros posicionais

Antes de começarmos a discutir o $INTEGERtipo de variáveis, precisamos entender o que realmente são e como diferem das variáveis ​​de ambiente. Variáveis ​​como $INTEGERos chamados parâmetros posicionais. Isso está descrito no padrão POSIX (Interface do sistema operacional portátil), seção 2.1 (ênfase minha):

  1. O shell executa uma função (consulte Comando de Definição de Função), interno (consulte Utilitários Internos Especiais), arquivo executável ou script, fornecendo os nomes dos argumentos como parâmetros posicionais numerados de 1 a n e o nome do comando (ou no caso de uma função em um script, o nome do script) como o parâmetro posicional numerado 0 (consulte Pesquisa e execução de comandos).

Por outro lado, variáveis ​​como $HOMEe $PATHsão variáveis ​​de ambiente. Sua definição é descrita na seção 8 da norma :

As variáveis ​​de ambiente definidas neste capítulo afetam a operação de vários utilitários, funções e aplicativos. Existem outras variáveis ​​de ambiente que são de interesse apenas para utilitários específicos. As variáveis ​​de ambiente que se aplicam apenas a um único utilitário são definidas como parte da descrição do utilitário.

Observe a descrição deles. Parâmetros posicionais devem aparecer na frente de um comando, ie command positional_arg_1 positional_arg_2.... Eles devem ser fornecidos pelo usuário para informar ao comando o que especificamente fazer. Quando você o fizer echo 'Hello' 'World', ele imprimirá as seqüências Helloe World, porque esses são parâmetros posicionais para echoas coisas nas quais você deseja echooperar. E echoé construído de tal forma que entende os parâmetros posicionais como strings a serem impressos (a menos que sejam um dos sinalizadores opcionais como -n). Se você fizer isso com um comando diferente, pode não entender o que HelloeWorldé porque talvez ele espere um número. Observe que os parâmetros posicionais não são "herdados" - um processo filho não conhece os parâmetros posicionais do pai, a menos que seja explicitamente passado ao processo filho. Freqüentemente, você vê parâmetros posicionais sendo passados ​​com scripts de wrapper - aqueles que talvez verifiquem a instância de um comando já existente ou adicionem parâmetros posicionais adicionais ao comando real que será chamado.

Por outro lado, as variáveis ​​de ambiente devem afetar vários programas. São variáveis ​​de ambiente , porque estão definidas fora do próprio programa (mais sobre isso abaixo). Certas variáveis ​​de ambiente, como HOMEou PATHpossuem formato específico, significado específico e terão o mesmo significado para cada programa. HOMEA variável terá o mesmo significado para qualquer utilitário externo /usr/bin/findou para o seu shell (e consequentemente para um script) - é o diretório inicial do nome de usuário sob o qual o processo é executado. Observe que variáveis ​​ambientais podem ser usadas para explicar comportamentos de comandos específicos, por exemploUIDA variável de ambiente pode ser usada para verificar se o script é executado com privilégios de root ou não e se ramifica para ações específicas adequadamente. As variáveis ​​de ambiente são herdáveis ​​- os processos filhos obtêm uma cópia do ambiente dos pais. Consulte também Se os processos herdam o ambiente do pai, por que precisamos exportar?

Em resumo, a principal distinção é que as variáveis ​​de ambiente são definidas fora do comando e não devem ser variadas (geralmente), enquanto parâmetros posicionais são coisas que devem ser processadas pelo comando e são alteradas.


Não apenas conceitos de shell

O que eu notei nos comentários é que você está misturando terminal e shell, e realmente recomendo que você leia sobre terminais reais que antes eram dispositivos físicos. Atualmente, o "terminal" ao qual estamos nos referindo normalmente, aquela janela com fundo preto e texto verde é na verdade software, um processo. Terminal é um programa que executa um shell, enquanto o shell também é um programa, mas aquele que lê o que você digita para executar (ou seja, se for um shell interativo; shells não interativos são scripts e sh -c 'echo foo'tipos de invocações). Mais sobre conchas aqui .

Essa é uma distinção importante, mas também é importante reconhecer que o terminal é um programa e, portanto, adere às mesmas regras de ambiente e parâmetros posicionais. Sua gnome-terminalquando iniciado vai olhar para sua SHELLvariável de ambiente, e desovar o shell padrão adequado para você, a menos que você especifique algum outro comando com -e. Digamos que mudei meu shell padrão para ksh - o gnome-terminal irá aparecer em kshvez de bash. Esse também é um exemplo de como o ambiente é usado pelos programas. Se eu disser explicitamente gnome-terminalcom -epara executar shell específico - ele vai fazê-lo, mas não vai ser permanente. Por outro lado, o ambiente deve permanecer inalterado (mais sobre isso posteriormente).

Portanto, como você pode ver, variáveis ​​de ambiente e posicionais são propriedades de um processo / comando, não apenas shell. Quando se trata de shell scripts, eles também seguem o modelo que foi definido pela linguagem de programação C. Tomemos, por exemplo, a mainfunção C , que normalmente se parece com

int main(int argc, char **argv)

, onde argcestá o número de argumentos da linha de comando e argvé efetivamente um conjunto de parâmetros da linha de comando; depois, há uma environfunção (no Linux man -e 7 environ) de acessar coisas como o caminho do diretório inicial do usuário, a lista de diretórios nos PATHquais podemos procurar executáveis ​​etc. Os scripts de shell também são modelados da mesma maneira. Na terminologia do shell, temos parâmetros posicionais $1, $2e assim por diante, enquanto $#é o número de parâmetros posicionais. Que tal $0? Esse é o nome do próprio executável, que também é modelado a partir da linguagem de programação C - argv[0]seria o nome do seu C "executável". E isso é verdade para a maioria das linguagens de programação e script .

Blocos interativos vs não interativos

Uma das coisas que eu já sugeri é a distinção entre shells interativos e não interativos . O prompt em que você digita os comandos - que é interativo, interage com o usuário. Por outro lado, quando você tem um script de shell ou executa bash -c''de forma não interativa.

E é aí que a distinção se torna importante. O shell que você já executa é um processo que foi gerado com parâmetros posicionais (para o bashshell de login é um "... cujo primeiro caractere do argumento zero é um - ou um iniciado com a opção --login".) ( Referência ) )

Por outro lado, scripts e shells lançados com a -copção podem aproveitar $1e $2argumentos. Por exemplo,

$ bash -c 'echo $1; stat $2' sh 'Hello World' /etc/passwd
Hello World
  File: '/etc/passwd'
  Size: 2913        Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d  Inode: 6035604     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2017-08-12 14:48:37.125879962 -0600
Modify: 2017-08-12 14:48:37.125879962 -0600
Change: 2017-08-12 14:48:37.137879811 -0600
 Birth: -

Observe que eu também usei shlá, porque uma pequena peculiaridade da -copção é pegar o primeiro parâmetro posicional e atribuí-lo $0, ao contrário de normalmente ser um nome do programa.

Outra coisa que é importante notar é que parâmetros posicionais são o que eu chamo de "framable". Observe como, lançamos pela primeira vez bashcom seus próprios parâmetros posicionais, mas esses parâmetros posicionais se tornaram parâmetros para echoe stat. E cada programa entende isso da sua maneira. Se dermos statuma string Hello Worlde não houver arquivo Hello World, isso produzirá um erro; bashtrata-o apenas como uma sequência simples, mas statespera que ela seja um nome de arquivo existente. Por outro lado, todos os programas concordariam que a variável de ambiente HOMEé um diretório (a menos que o programador a codificasse de maneira irracional).


Podemos mexer com variáveis ​​de ambiente e parâmetros posicionais?

Tecnicamente, podemos mexer com ambos, mas não devemos mexer com variáveis ​​de ambiente, enquanto geralmente precisamos fornecer parâmetros posicionais. Podemos executar comandos no shell anexando uma variável, por exemplo:

$ hello=world bash -c 'echo $hello'
world

Também podemos colocar variáveis ​​no ambiente usando simplesmente export variable=valuedentro do shell ou script. Ou podemos executar um comando com ambiente completamente vazio com env -c command arg1 arg2. No entanto, normalmente não é recomendável mexer com o ambiente, especialmente usando variáveis ​​maiúsculas ou substituindo as variáveis ​​de ambiente já existentes. Observe que isso é recomendado, embora não seja um padrão.

Para parâmetros posicionais, a maneira de defini-los é óbvia, basta anexá-los ao comando, mas também existem maneiras de defini-los de outra maneira , além de alterar a lista desses parâmetros por meio do shiftcomando.

Em conclusão, o objetivo desses dois é diferente e existe por uma razão. Espero que as pessoas tenham entendido essa resposta e tenha sido divertido lê-la exatamente como escrevi essa resposta.


Nota sobre o comando set

O setcomando, de acordo com o manual, se comporta da seguinte maneira (do manual do bash, ênfase adicionada):

Sem opções, o nome e o valor de cada variável do shell são exibidos em um formato que pode ser reutilizado como entrada para definir ou redefinir as variáveis ​​definidas no momento.

Em outras palavras, setanalisa variáveis ​​específicas do shell, algumas das quais estão no ambiente, por exemplo HOME. Por outro lado, os comandos gostam enve printenvexaminam a variável de ambiente real com a qual um comando é executado. Veja também isso .

Sergiy Kolodyazhnyy
fonte
"No shell interativo, você não pode fazer referência a $ 1, $ 2 e assim por diante." Existe uma referência direta para isso? Encontrei casos estranhos em que eles são definidos em um ambiente de shell interativo e não tenho certeza se isso seria considerado 'não-padrão' ou não.
user5359531
@ user5359531 Honestamente, não tenho muita certeza de onde obtive isso. Provavelmente porque, quando publiquei a resposta em 2017, provavelmente referi que você não pode fazer algo assim, 1="foo"mas depois descobri que, por definição do POSIX, uma "palavra" (que é o nome de um objeto, como variável ou função) não pode ser iniciada com um dígito (veja uma pergunta que eu postei sobre o tópico ). Os parâmetros posicionais aparentemente são exceção a esta regra.
Sergiy Kolodyazhnyy
@ user5359531 Removi a parte da resposta, pois não é particularmente precisa. Você pode fazer referência $1, $2e assim por diante, no shell interativo e, de fato, isso é feito com o setcomando, geralmente para contornar a /bin/shlimitação de não ter matrizes. Obrigado por trazer isso à minha atenção. Também vou editar a resposta nos próximos dias, pois ela precisa de um pouco de polimento extra e uma atualização.
Sergiy Kolodyazhnyy
Obrigado pelo esclarecimento. O problema que estou tendo é que conda, quando você executa source conda/bin/activate, ele verifica se $1, $2etc., está definido, para determinar se foi executado como um script com argumentos ou não. Isso acaba sendo interrompido em sistemas que possuem aqueles configurados no ambiente interativo por algum motivo. Espero descobrir se esse comportamento não padrão é uma falha no sistema para definir essas variáveis ​​no ambiente interativo ou no programa para usá-las para determinar se foi executado como um script.
user5359531
@ user5359531 Sugiro que você envie um relatório de bug aos condadesenvolvedores ou a quem é o autor original desse script, pois a verificação de ${N}parâmetros é definitivamente o caminho errado. Há perguntas sobre o mesmo tema aqui e aqui , e mais ou menos portátil maneira é verificar se ${0}é o mesmo que nome do script, enquanto que bashna verdade tem variável de ambiente para o efeito
Sergiy Kolodyazhnyy
4

As $1, $2, $3, ..., ${10}, ${11}variáveis ​​são chamadas de parâmetros posicionais e são abordadas na seção manual do bash3.4.1

3.4.1 Parâmetros posicionais

Um parâmetro posicional é um parâmetro indicado por um ou mais dígitos, exceto o dígito único 0. Os parâmetros posicionais são atribuídos a partir dos argumentos do shell quando são chamados e podem ser reatribuídos usando o comando set builtin. O parâmetro posicional N pode ser referenciado como $ {N} ou $ N quando N consiste em um único dígito. Os parâmetros posicionais não podem ser atribuídos a com instruções de atribuição. Os recursos internos de configuração e mudança são usados ​​para defini-los e desativá-los (consulte Comandos internos do shell). Os parâmetros posicionais são substituídos temporariamente quando uma função shell é executada (consulte Funções Shell).

Quando um parâmetro posicional que consiste em mais de um dígito único é expandido, ele deve ser colocado entre chaves.

Quanto a , $?e $0esses parâmetros especiais são abordados na próxima seção3.4.2

3.4.2 Parâmetros especiais

O shell trata vários parâmetros especialmente. Esses parâmetros podem ser referenciados apenas; a atribuição a eles não é permitida.

...

?

($?) Expande para o status de saída do pipeline de primeiro plano executado mais recentemente.

0 0

($ 0) Expande para o nome do shell ou script do shell. Isso é definido na inicialização do shell. Se o Bash for chamado com um arquivo de comandos (consulte Scripts do Shell), $ 0 será definido como o nome desse arquivo. Se o Bash for iniciado com a opção -c (consulte Invocação do Bash), $ 0 será definido como o primeiro argumento após a sequência a ser executada, se houver alguma. Caso contrário, ele será definido como o nome do arquivo usado para chamar o Bash, conforme indicado pelo argumento zero.

Jesse_b
fonte
4

$1, $2... são parâmetros posicionais , não são variáveis, muito menos variáveis ​​de ambiente.

Em Bourne-like terminologia concha, $somethingé chamado de parâmetro de expansão (também cobre ${something#pattern}e mais em alguns escudos como ${array[x]}, ${param:offset}, ${x:|y}e muitos operadores de expansão mais).

Existem diferentes tipos de parâmetros:

  • variáveis como $foo,$PATH
  • parâmetros posicionais ( $1, $2... os argumentos que seu script recebeu)
  • outros parâmetros especiais, como $0, $-, $#, $*, $@, $$, $!, $?...

os nomes de variáveis ​​em shells semelhantes a Bourne devem começar com um caractere alfabético (qualquer um reconhecido pelo código do idioma ou limitado a a-zA-Z, dependendo do shell) e sublinhado e seguido por zero ou mais caracteres alfanuméricos ou sublinhados.

Dependendo do shell, as variáveis ​​podem ter tipos diferentes (escalar, matriz, hash) ou receber determinados atributos (somente leitura, exportados, minúsculos ...).

Algumas dessas variáveis são criados pelo shell ou têm um significado especial para o shell (como $OPTIND, $IFS, $_...)

As variáveis ​​do shell que possuem o atributo export são automaticamente exportadas como variáveis ​​de ambiente para os comandos que o shell executa.

Variável de ambiente é um conceito separado das variáveis ​​do shell. Exportar uma variável de shell não é a única maneira de passar uma variável de ambiente para a execução de um comando.

VAR=foo
export VAR
printenv VAR

passará uma VARvariável de ambiente para o printenvcomando (que estamos dizendo para imprimir seu conteúdo), mas você também pode:

env VAR=foo printenv VAR

ou:

perl -e '$ENV{VAR}="foo"; exec "printenv", "VAR"'

por exemplo.

As variáveis ​​de ambiente podem ter qualquer nome (podem conter qualquer caractere, mas =podem até estar vazias). Não é uma boa idéia atribuir um nome que não seja compatível com o nome de variável de shell semelhante a Bourne a uma variável de ambiente, mas é possível:

$ env '#+%=whatever' printenv '#+%'
whatever

Os shells mapearão as variáveis ​​de ambiente que receberem para as variáveis ​​do shell apenas para as variáveis ​​de ambiente cujo nome são variáveis ​​válidas do shell (e, em alguns shells, ignoram algumas especiais, como $IFS).

Portanto, enquanto você poderia passar uma 1variável de ambiente para um comando:

$ env '1=whatever' printenv 1
whatever

Isso não significa que chamar um shell com essa variável de ambiente definiria o valor do $1parâmetro:

$ env '1=whatever' sh -c 'echo "$1"' script-name foo bar
foo
Stéphane Chazelas
fonte
3

Não, esses são parâmetros do script. Por exemplo, se você chamar seu script como:

mynicescript.sh one two three

então, dentro do script, haverá esses parâmetros disponíveis como

$1 = one
$2 = two
$3 = three

e $ 0 é o nome do próprio script.

Portanto, quando você está fora do script, essas variáveis ​​não estão disponíveis (exceto $ 0, que exibe / bin / bash - o próprio shell).

Jaroslav Kucera
fonte
"Então, quando você está fora do script, essas variáveis ​​não estão disponíveis" O que você quer dizer com "fora do script" , porque eu posso ver os valores dessas variáveis ​​no meu terminal.
user7681202
2
@ user7681202: Quais você pode ver no seu terminal? $0apontará para o processo atual do terminal (provavelmente bash) e $?é simplesmente o código de saída do último processo.
Jesse_b
Eu tentei executar o gnome-terminalargumento com ( gnome-terminal Hello World). Eu pude ver $0, mas não pude ver $1e $2.
user7681202
@Jesse_b Obrigado, adicionei o conteúdo de $ 0 à resposta.
Jaroslav Kucera
1
@ user7681202 O gnome-terminal não é shell, é emulador de terminal (como xterm, konsole etc.). O shell é executado dentro do terminal e pode ser bash / sh / zsh / tcsh e muito mais. O script é um arquivo executável com cabeçalho adequado (como #! / Bin / bash) e conteúdo interpretável pelo shell especificado na máscara. Ele geralmente usa sufixo .sh
Jaroslav Kucera