Existe um comando de saída mais abrangente que faz mais de printenv
?
printenv
imprime apenas variáveis de ambiente , o que pode ser considerado uma vantagem. Mas se você quiser imprimir também variáveis de shell, use echo "$x"
(ou printf '%s\n' "$x"
, que é mais robusto ) em vez de printenv x
.
explicação da steeldriver dessas questões é útil e correta, mas estou apresentando o tópico de outra maneira aqui.
printenv
é um comando externo - não embutido no seu shell, mas um programa separado do seu shell. Ele mostra as variáveis de ambiente , que são aquelas que ele herda do shell que você usa para executá-lo. No entanto, os shells não passam todas as variáveis para os subprocessos 'ambientes . Em vez disso, eles mantêm uma distinção entre quais variáveis são variáveis de ambiente e quais não são. (Aqueles que não são, muitas vezes, chamam variáveis do shell .)
Variáveis da Shell
Para ver como isso funciona, tente esses comandos, que estão entre (
)
, para que eles sejam independentes 1 um do outro. Individualmente, cada um desses comandos funciona da mesma forma quando você o executa sem o (
)
, mas as variáveis que você cria nos comandos anteriores ainda existiriam nos comandos posteriores. Executar os comandos em subshells evita isso.
Criar uma nova variável e, em seguida, executar um comando externo, não passa a variável para o ambiente do comando. Exceto no caso incomum que você já tem uma variável de ambiente x
, este comando não produz saída:
(x=foo; printenv x)
A variável é atribuída no shell, no entanto. Este comando gera foo
:
(x=foo; echo "$x")
O shell suporta a sintaxe para passar uma variável para o ambiente de um comando sem afetar o ambiente atual do shell. Isso gera foo
:
x=foo printenv x
(Isso funciona em um subshell, também, é claro - (x=foo printenv x)
- mas eu mostrei isso sem o (
)
porque quando você usa essa sintaxe, nada é definido para o seu shell atual, então usar um subshell é desnecessário para evitar que os comandos subsequentes sejam afetados.)
Imprime foo
e, em seguida, imprime bar
:
(x=bar; x=foo printenv x; echo "$x")
Exportando
Quando você exporta uma variável, ela é passada automaticamente para os ambientes de todos os comandos externos subseqüentes, a partir do mesmo shell. O comando export
faz isso. Você pode usá-lo antes de definir a variável, depois de defini-la, ou até definir a variável no comando export
. Todos estes print foo
:
(x=foo; export x; printenv x)
(export x; x=foo; printenv x)
(export x=foo; printenv x)
Não há comando unexport
. Mesmo que você possa exportar uma variável antes de configurá-la, a desativação de uma variável também a desperta, o que significa que ela não imprime nada, em vez de imprimir bar
:
(x=foo; export x; unset x; x=bar; printenv x)
Mas alterando o valor de uma variável depois de exportar , afeta o valor exportado. Isso imprime foo
e, em seguida, bar
:
(export x=foo; printenv x; x=bar; printenv x)
Como outros processos, o próprio shell herda as variáveis de ambiente de seu processo pai. Essas variáveis estão presentes inicialmente no ambiente do seu shell e são exportadas automaticamente - ou permanecem exportadas, se você decidir pensar dessa maneira. Isso imprime foo
(lembre-se, VAR=val cmd
runs cmd
com VAR
definido como val
em seu ambiente):
x=foo bash -c 'printenv x'
Variáveis definidas em processos-filhos não afetam o processo pai, mesmo se elas forem exportadas. Isso imprime foo
(não bar
):
(x=foo; bash -c 'export x=bar'; echo "$x")
Subshells
Um subshell também é um processo filho 2 ; isso também imprime foo
:
(x=foo; (export x=bar); echo "$x")
Isso deve deixar mais claro porque incluí a maioria desses comandos em (
)
para executá-los em subshells.
Subshells são especiais, no entanto. Diferentemente de outros subprocessos, como aqueles criados quando você executa um comando externo como printenv
ou bash
, um subshell herda a maior parte do estado do seu shell pai . Em particular, os subshells herdam mesmo variáveis que não são exportadas . Assim como (x=foo; echo "$x")
imprime foo
, o mesmo acontece com (x=foo; (echo "$x"))
.
A variável não exportada ainda não é exportada no subshell - a menos que você exporte - assim, assim como (x=foo; printenv x)
não imprime nada, o mesmo acontece com (x=foo; (printenv x))
.
Um subshell é um tipo especial de subprocesso que é um shell. Nem todos os subprocessos que são shells são subshells. O shell criado executando bash
não é uma subshell e não herda variáveis não-exportadas. Portanto, esse comando imprime uma linha em branco (porque echo
imprime uma nova linha mesmo quando chamado com um argumento vazio):
(x=foo; bash -c 'echo "$x"')
Por que PS1
não é uma variável de ambiente (e geralmente não deve ser uma)
Por fim, por que as variáveis de prompt, como PS1
, são variáveis de shell, mas não variáveis de ambiente, as razões são:
- Eles são necessários apenas no shell, não em outros programas.
- Eles são definidos para cada shell interativo e os shells não interativos não precisam deles. Ou seja, eles não precisam ser herdados.
- A tentativa de passar
PS1
para um novo shell normalmente falha, porque o shell geralmente redefine PS1
.
O ponto # 3 merece um pouco mais de explicação, mas se você nunca tentar tornar PS1
uma variável de ambiente, provavelmente você não precisará precisar para conhecer os detalhes.
Quando o Bash começa de forma não interativa, ele desativa PS1
.
Quando um shell Bash não interativo é iniciado, sempre 3 é desfeito PS1
. Isso imprime uma linha em branco (não foo
):
PS1=foo bash -c 'echo "$PS1"'
Para verificar se ele está realmente não definido, e não apenas definido, mas vazio, você pode executar isso, que imprime unset
:
PS1=foo bash -c 'if [[ -v PS1 ]]; then echo set; else echo unset; fi'
Para verificar se isso é independente de outro comportamento de inicialização, você pode tentar passar qualquer combinação de --login
, --norc
ou --posix
antes de -c
ou definir BASH_ENV
para o caminho de algum script ( por exemplo, BASH_ENV=~/.bashrc PS1=foo bash ...
) ou ENV
se você passou --posix
. Em nenhum caso, um shell Bash não-interativo falhará ao remover PS1
.
O que isto significa é que se você exportar PS1
e executar um shell não interativo que ele mesmo executa um shell interativo, ele não configurará o valor PS1
que você definiu originalmente. Por esta razão - e também porque outros shells além do Bash (como o Ksh) não se comportam da mesma maneira, e o modo como você escreve PS1
para o Bash nem sempre funciona para esses shells - eu recomendo que não tente fazer PS1
uma variável de ambiente. Basta editar ~/.bashrc
para definir o prompt desejado.
Quando o Bash inicia interativamente, muitas vezes define ou redefine PS1
.
Por outro lado, se você
anular PS1
e executar um shell Bash interativo, mesmo que você o impeça de executar comandos de scripts de inicialização passando --norc
, ele ainda será automaticamente definido PS1
para um valor padrão. A execução de env -u PS1 bash --norc
fornece um shell Bash interativo com PS1
definido como \s-\v$
. Como o Bash expande \s
para o nome do shell e \v
para o número da versão, isso mostra bash-4.3$
como o prompt no Ubuntu 16.04 LTS. Observe que definir o valor de PS1
como a sequência de caracteres vazia não é o mesmo que desativá-la. Conforme explicado abaixo, a execução de PS1= bash
fornece um shell interativo com comportamento de inicialização estranho. Você deve evitar exportar PS1
quando estiver definido para a sequência vazia, em uso prático, a menos que você entenda e deseje esse comportamento.
No entanto, se você definir PS1
e executar um shell Bash interativo - e ele não for removido por um shell intermediário não interativo - ele manterá esse valor ... até um script de inicialização como o global /etc/profile
(para cascas de login) ou /etc/bash.bashrc
, ou seu por usuário ~/.profile
, ~/.bash_login
ou ~/.bash_profile
(todos para shells de login) ou ~/.bashrc
redefine.
Mesmo se você editar esses arquivos para impedir que eles definam PS1
- que, no caso de /etc/profile
e /etc/bash.bashrc
, eu não recomendaria fazer isso de qualquer maneira, pois eles afetam todos os usuários - não é possível realmente confiar nisso. Como mencionado acima, os shells interativos iniciados a partir de shells não interativos não terão PS1
, a menos que você tenha que resetá-lo e reexportá-lo no shell não interativo. Além disso, você deve pensar duas vezes antes de fazer isso, porque é comum que o código shell (incluindo as funções do shell que você pode ter definido) verifique PS1
para determinar se o shell em execução é interativo ou não interativo.
A verificação de PS1
é uma maneira comum de determinar se o shell atual é interativo.
É por isso que é tão importante para shells não-interativos Bash 4 para remover PS1
automaticamente. Como seção 6.3.2 Esta Shell é Interativa? do Manual de referência do bash diz:
[S] scripts tartup podem examinar a variável PS1
; ela não é definida em shells não interativos e configurada em shells interativos.
Para ver como isso funciona, veja o exemplo lá. Ou confira os usos do mundo real no Ubuntu.Por padrão, /etc/profile
no Ubuntu inclui:
if [ "$PS1" ]; then
if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "'id -u'" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
/etc/bash.bashrc
, que não deve fazer nada quando o shell não é interativo, tem:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
Sutilezas de diferentes métodos de verificação de interatividade:
Para atingir o mesmo objetivo, /etc/skel/.bashrc
, que é copiado nos diretórios iniciais dos usuários quando suas contas são criadas (assim o ~/.bashrc
é provavelmente semelhante), tem:
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
Essa é a outra maneira comum de verificar se um shell é interativo: veja se o texto obtido por expandindo o parâmetro especial -
( escrevendo $-
) contém a letra i
. Geralmente isso tem exatamente o mesmo efeito. Suponha, no entanto, que você não tenha modificado o código mostrado acima que aparece por padrão nos scripts de inicialização do Bash no Ubuntu, e que:
- você exporta
PS1
como uma variável de ambiente, e
- está definido, mas para o valor vazio , e
- você inicia um shell Bash interativo ...
Em seguida, /etc/profile
(se for um shell de login) ou /etc/bash.bashrc
não executará os comandos que normalmente executam para shells interativos. ~/.bashrc
ainda será.
Se você quiser verificar se um shell é interativo usando PS1
e obter a resposta correta mesmo quando PS1
estiver definido, mas vazio, use [[ -v PS1 ]]
ou [ -v PS1 ]
/ test -v PS1
. Observe, no entanto, que a palavra-chave [[
e o teste -v
dos co-processadores [
e test
embutidos são específicos do Bash. Nem todas as outras granadas do estilo Bourne as aceitam. Então você deve não usá-los em scripts como ~/.profile
e /etc/profile
que podem ser executados em outros shells (ou por um gerenciador de exibição quando você faz login graficamente), a menos que você tenha alguma outra coisa no script que verifica o que o shell está executando e apenas executa comandos específicos do Bash quando esse shell é Bash (por exemplo, verificando $BASH_VERSION
).
Notas
1 Este artigo explica detalhadamente as sub-unidades. 3.2.4.3 Comandos de agrupamento do manual de referência do Bash explica o (
)
syntax.
2 Observe que há circunstâncias sob o qual comandos são executados em subshells mesmo com a sintaxe (
)
não é usada. Por exemplo, quando você tem comandos separados por |
em um pipeline , o Bash é executado cada um deles em um subshell (a menos que a opção de shell lastpipe
está definido).
3 Exceto para subshells . Indiscutivelmente, isso não é nem uma exceção, já que subshells não "iniciam" no sentido usual que queremos dizer quando falamos sobre isso. (Eles não têm realmente um comportamento de inicialização significativo.) Observe que quando você executa bash
- com ou sem argumentos - dentro de um shell Bash, isso cria um subprocesso que é um shell, mas não é um subshell.
4 Observe que nem todos os shells - nem mesmo todos os Conchas estilo Bourne - se comportam dessa maneira. Mas o Bash sim, e é muito comum que o código do Bash, incluindo o código em scripts de inicialização, dependa dele.