Por que as variáveis não são como $ PS1 no printenv?

5

Pelo que posso dizer, printenv exibe variáveis de ambiente, mas por que não vejo outras variáveis como PS1 para personalizar o prompt do shell?

O que exatamente é printenv de saída e por que ele não pega PS1 ? Existe um comando de saída mais abrangente que faz mais de printenv ?

    
por Aruka J 29.09.2017 / 17:06

2 respostas

5

Isso porque PS1 normalmente não é exportado.

Variáveis de ambiente são usadas para definir o ambiente de execução de processos filhos; já que PS1 realmente tem significância dentro de um shell interativo, normalmente não há nenhum ponto exportando-o - é apenas uma simples variável shell .

Se você iniciar um filho interativo shell , ele lerá e definirá seu PS1 do arquivo de recurso do shell, como ~/.bashrc

Se você export PS1 , então você verá na saída printenv . Alternativamente, você pode ver variáveis de shell simples usando o bash builtin set , conforme descrito aqui Como listar todos os nomes de variáveis e seus valores atuais?

    
por steeldriver 29.09.2017 / 17:20
3
  

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:

  1. Eles são necessários apenas no shell, não em outros programas.
  2. 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.
  3. 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:

  1. você exporta PS1 como uma variável de ambiente, e
  2. está definido, mas para o valor vazio , e
  3. 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.

    
por Eliah Kagan 04.10.2017 / 03:15