Quais são os operadores de controle e redirecionamento do shell?

245

Muitas vezes vejo tutoriais online que conectam vários comandos com símbolos diferentes. Por exemplo:

command1 |  command2
command1 &  command2
command1 || command2    
command1 && command2

Outros parecem estar conectando comandos aos arquivos:

command1  > file1
command1  >> file1

O que são essas coisas? O que eles chamaram? O que eles fazem? Existem mais deles?


Meta thread sobre esta questão. .

terdon
fonte

Respostas:

340

Esses são chamados de operadores de shell e, sim, existem mais deles. Vou dar uma breve visão geral dos mais comuns entre as duas principais classes, operadores de controle e operadores de redirecionamento , e como eles funcionam com relação ao shell bash.

A. Operadores de controle

Na linguagem de comando do shell, um token que executa uma função de controle.
É um dos seguintes símbolos:

&   &&   (   )   ;   ;;   <newline>   |   ||

E |&na festança.

A não! é um operador de controle, mas uma Palavra Reservada . Torna-se um NOT [operador de negação] lógico dentro das Expressões Aritméticas e nas construções de teste internas (embora ainda exija um delimitador de espaço).

A.1 Listar terminadores

  • ; : Executará um comando após o término do outro, independentemente do resultado do primeiro.

    command1 ; command2

    O primeiro command1é executado, em primeiro plano e, uma vez concluído, command2será executado.

    Uma nova linha que não esteja em um literal de sequência ou após determinadas palavras-chave não é equivalente ao operador de ponto e vírgula. Uma lista de ;comandos simples delimitados ainda é uma lista - como no analisador do shell ainda deve continuar lendo os comandos simples que seguem um ;comando simples delimitado antes da execução, enquanto uma nova linha pode delimitar uma lista de comandos inteira - ou uma lista de listas. A diferença é sutil, mas complicada: dado que o shell não tem um imperativo prévio para a leitura de dados após uma nova linha, a nova linha marca um ponto em que o shell pode começar a avaliar os comandos simples em que já leu, enquanto um ;ponto e vírgula faz não.

  • & : Isso executará um comando em segundo plano, permitindo que você continue trabalhando no mesmo shell.

     command1 & command2

    Aqui, command1é iniciado em segundo plano e command2começa a ser executado em primeiro plano imediatamente, sem esperar pela command1saída.

    Uma nova linha depois command1é opcional.

A.2 Operadores lógicos

  • && : Usado para criar listas AND, ele permite que você execute um comando apenas se outro sair com êxito.

     command1 && command2

    Aqui, command2será executado após a command1conclusão e somente se command1for bem-sucedido (se o código de saída for 0). Ambos os comandos são executados em primeiro plano.

    Este comando também pode ser escrito

    if command1
    then command2
    else false
    fi

    ou simplesmente if command1; then command2; fise o status de retorno for ignorado.

  • || : Usado para criar listas OR, ele permite executar um comando apenas se outro sair sem êxito.

     command1 || command2

    Aqui, command2só será executado se houver command1falha (se ele retornar um status de saída diferente de 0). Ambos os comandos são executados em primeiro plano.

    Este comando também pode ser escrito

    if command1
    then true
    else command2
    fi

    ou de forma mais curta if ! command1; then command2; fi.

    Observe que &&e ||são associativos à esquerda; consulte Precedência dos operadores lógicos do shell &&, || Para maiores informações.

  • !: É uma palavra reservada que atua como operador "not" (mas deve ter um delimitador), usada para negar o status de retorno de um comando - retorne 0 se o comando retornar um status diferente de zero, retorne 1 se retornar o status 0 Também um NÃO lógico para o testutilitário.

    ! command1
    
    [ ! a = a ]

    E um verdadeiro operador NOT dentro de Expressões Aritméticas:

    $ echo $((!0)) $((!23))
    1 0

A.3 Operador de tubo

  • |: O operador do tubo passa a saída de um comando como entrada para outro. Um comando criado a partir do operador de tubulação é chamado de pipeline .

     command1 | command2

    Qualquer saída impressa por command1é passada como entrada para command2.

  • |&: Esta é uma abreviação para 2>&1 |bash e zsh. Ele passa a saída padrão e o erro padrão de um comando como entrada para outro.

    command1 |& command2

A.4 Outra pontuação da lista

;;é usado apenas para marcar o final de uma declaração de caso . Ksh, bash e zsh também suportam ;&passar para o próximo caso e ;;&(não no ATT ksh) para continuar e testar os casos subsequentes.

(e )são usados ​​para agrupar comandos e iniciá-los em um subshell. {e }também comandos de grupo, mas não os inicie em um subshell. Veja esta resposta para uma discussão sobre os vários tipos de parênteses, colchetes e chaves na sintaxe do shell.

B. Operadores de redirecionamento

Operador de redirecionamento

Na linguagem de comando do shell, um token que executa uma função de redirecionamento. É um dos seguintes símbolos:

<     >     >|     <<     >>     <&     >&     <<-     <>

Isso permite que você controle a entrada e a saída de seus comandos. Eles podem aparecer em qualquer lugar dentro de um comando simples ou podem seguir um comando. Os redirecionamentos são processados ​​na ordem em que aparecem, da esquerda para a direita.

  • < : Fornece entrada para um comando.

    command < file.txt

    O acima será executado commandno conteúdo de file.txt.

  • <>: igual ao acima, mas o arquivo está aberto no modo de leitura + gravação em vez de somente leitura :

    command <> file.txt

    Se o arquivo não existir, ele será criado.

    Esse operador raramente é usado porque os comandos geralmente são lidos apenas pelo stdin, embora possam ser úteis em várias situações específicas .

  • > : Direciona a saída de um comando para um arquivo.

    command > out.txt

    O acima irá salvar a saída de commandcomo out.txt. Se o arquivo existir, seu conteúdo será sobrescrito e, se não existir, será criado.

    Esse operador também é frequentemente usado para escolher se algo deve ser impresso com erro padrão ou saída padrão :

    command >out.txt 2>error.txt

    No exemplo acima, >redirecionará a saída padrão e 2>redirecionará o erro padrão. A saída também pode ser redirecionada usando 1>, mas, como esse é o padrão, 1ele geralmente é omitido e é escrito simplesmente como >.

    Assim, para executar commandno file.txte salvar sua saída em out.txte quaisquer mensagens de erro em error.txtque você deve executar:

    command < file.txt > out.txt 2> error.txt
  • >|: Faz o mesmo que >, mas substituirá o destino, mesmo que o shell tenha sido configurado para recusar a substituição (com set -Cou set -o noclobber).

    command >| out.txt

    Se out.txtexistir, a saída de commandsubstituirá seu conteúdo. Se não existir, será criado.

  • >>: Faz o mesmo que >, exceto que, se o arquivo de destino existir, os novos dados serão anexados.

    command >> out.txt

    Se out.txtexistir, a saída de commandserá anexada a ele, depois do que já estiver nele. Se não existir, será criado.

  • &>, >&, >>&E &>>: (não padronizada). Redirecione o erro padrão e a saída padrão, substituindo ou anexando, respectivamente.

    command &> out.txt

    O erro padrão e a saída padrão commandserão salvos out.txt, substituindo seu conteúdo ou criando-o se ele não existir.

    command &>> out.txt

    Como acima, exceto que, se out.txtexistir, a saída e o erro de commandserão anexados a ele.

    A &>variante se origina em bash, enquanto a >&variante vem do csh (décadas antes). Ambos conflitam com outros operadores de shell POSIX e não devem ser usados ​​em shscripts portáteis .

  • <<: Um documento aqui. É frequentemente usado para imprimir seqüências de várias linhas.

     command << WORD
         Text
     WORD

    Aqui, commandlevará tudo até encontrar a próxima ocorrência de WORD, Textno exemplo acima, como entrada. Embora WORDseja frequente EoFou variada, pode ser qualquer string alfanumérica (e não apenas) que você desejar. Quando WORDé citado, o texto no documento aqui é tratado literalmente e nenhuma expansão é executada (em variáveis, por exemplo). Se não estiver entre aspas, as variáveis ​​serão expandidas. Para mais detalhes, consulte o manual do bash .

    Se você deseja canalizar a saída command << WORD ... WORDdiretamente para outro comando ou comandos, terá que colocar o canal na mesma linha que << WORD, não poderá colocá-lo após o WORD final ou na linha seguinte. Por exemplo:

     command << WORD | command2 | command3...
         Text
     WORD
  • <<<: Aqui strings, semelhantes aos documentos aqui, mas destinadas a uma única linha. Eles existem apenas na porta Unix ou rc (onde se originou), zsh, algumas implementações de ksh, yash e bash.

    command <<< WORD

    Tudo o que é dado como WORDé expandido e seu valor é passado como entrada para command. Isso geralmente é usado para passar o conteúdo de variáveis ​​como entrada para um comando. Por exemplo:

     $ foo="bar"
     $ sed 's/a/A/' <<< "$foo"
     bAr
     # as a short-cut for the standard:
     $ printf '%s\n' "$foo" | sed 's/a/A/'
     bAr
     # or
     sed 's/a/A/' << EOF
     $foo
     EOF

Alguns outros operadores ( >&-, x>&y x<&y) podem ser usados ​​para fechar ou duplicar descritores de arquivo. Para detalhes sobre eles, consulte a seção relevante do manual do seu shell ( aqui, por exemplo, para o bash).

Isso abrange apenas os operadores mais comuns de conchas do tipo Bourne. Alguns shells possuem alguns operadores de redirecionamento adicionais próprios.

Ksh, bash e zsh também têm construções <(…), >(…)e =(…)(essa última zshapenas). Estes não são redirecionamentos, mas substituem o processo .

terdon
fonte
2
Provavelmente seria interessante notar que nem todas as conchas são iguais e destacando especificamente os recursos específicos do bash.
Greg Hewgill
1
@GregHewgill sim, eu saí de lá dizendo que estou discutindo a respeito bash. Isso está sendo preparado como uma sessão de perguntas e respostas canônicas para fechar as várias perguntas "O que essa coisa estranha faz" e a maioria delas é de usuários do bash. Espero que outra pessoa participe e responda por conchas que não sejam do bash, mas destacar os específicos do bash faz muito sentido. Vou ter que verificar, porém, não sei o que estão em cima da minha cabeça.
terdon
&>, >>>E <<<são todos para não-POSIX como é a referência à não-somente caracteres não-alphanum em nome de um aqui-doc. Esta resposta também discute muito pouco sobre como eles funcionam - por exemplo, é quase pior do que inútil falar sobre um comando simples e um comando sem explicar o que são e como o shell decide.
mikeserv
@mikeserv thanks. Eles trabalham no bash e no zsh. Não sei o que é realmente específico para o bash nessa lista. Eu deveria passar por isso e adicionar as conchas em que cada um trabalha, mas isso envolveria descobrir primeiro.
terdon
1
@ Arc676 Não, eles não avaliam como verdadeiro ou falso, esse é um contexto completamente diferente. Isso significa apenas que um valor de saída diferente de 0 indica um problema (não false) e um código de saída 0 indica êxito (não true). Sempre foi assim e é bastante padrão. Um código de saída diferente de 0 indica um erro em todos os ambientes que conheço.
terdon
60

Aviso sobre '>'

Iniciantes em Unix que acabaram de aprender sobre o redirecionamento de E / S ( <e >) geralmente tentam coisas como

comando ... input_file > the_same_file

ou

comando … < arquivo      > the_same_file

ou, quase equivalente,

arquivo de gato | command …> the_same_file

( grep, sed, cut, sort, E spellsão exemplos de comandos que pessoas são tentadas a usar em construções como estas.) Os usuários são surpreendidos ao descobrir que esses cenários resultar no ficheiro de se tornar vazio.

Uma nuance que não parece ser mencionada na outra resposta pode ser encontrada à espreita na primeira frase da seção Redirecionamento do bash (1) :

Antes de um comando ser executado, sua entrada e saída podem ser redirecionadas usando uma notação especial interpretada pelo shell.

As cinco primeiras palavras devem estar em negrito, itálico, sublinhado, aumentado, piscando, coloridas em vermelho e marcadas com um ponto de exclamação no triângulo vermelhoícone, para enfatizar o fato de que o shell executa os redirecionamentos solicitados antes que o comando seja executado . E lembre-se também

O redirecionamento da saída faz com que o arquivo… seja aberto para gravação…. Se o arquivo não existir, ele será criado; se existir, será truncado para tamanho zero.

  1. Então, neste exemplo:

    sort roster > roster

    o shell abre o rosterarquivo para gravação, truncando-o (ou seja, descartando todo o seu conteúdo), antes que o sortprograma comece a ser executado. Naturalmente, nada pode ser feito para recuperar os dados.

  2. Alguém poderia ingenuamente esperar que

    tr "[:upper:]" "[:lower:]" < poem > poem

    pode ser melhor. Como o shell lida com os redirecionamentos da esquerda para a direita, ele abre poempara leitura (para tra entrada padrão) antes de abri-lo para gravação (para saída padrão). Mas isso não ajuda. Mesmo que essa sequência de operações produza dois identificadores de arquivo, ambos apontam para o mesmo arquivo. Quando o shell abre o arquivo para leitura, o conteúdo ainda está lá, mas ainda é bloqueado antes da execução do programa. 

Então o que fazer sobre isso?

As soluções incluem:

  • Verifique se o programa que você está executando possui capacidade própria e interna para especificar para onde vai a saída. Isso geralmente é indicado por um token -o(ou --output=). Em particular,

    sort roster -o roster

    é aproximadamente equivalente a

    sort roster > roster

    exceto, no primeiro caso, o sortprograma abre o arquivo de saída. E é inteligente o suficiente para não abrir o arquivo de saída até que depois de ter lido todo o arquivo (s) de entrada.

    Da mesma forma, pelo menos algumas versões de sedter um -i(edit i opção n Place) que pode ser usado para escrever a saída de volta para o arquivo de entrada (de novo, após todas as entradas foram lidos). Editores como ed/ ex, emacs, pico, e vi/ vim permitir que o usuário editar um arquivo de texto e salvar o texto editado no arquivo original. Observe que ed(pelo menos) pode ser usado de maneira não interativa.

    • vipossui um recurso relacionado. Se você digitar , ele gravará o conteúdo do buffer de edição , lerá a saída e o inserirá no buffer (substituindo o conteúdo original).:%!commandEntercommand
  • Simples mas efetivo:

    comando ... input_file > temp_file   && mv temp_file  input_file

    Isso tem a desvantagem de que, se input_filefor um link, ele (provavelmente) será substituído por um arquivo separado. Além disso, o novo arquivo será de sua propriedade, com proteções padrão. Em particular, isso implica o risco de que o arquivo acabe sendo legível pelo mundo, mesmo que o original input_filenão tenha sido.

    Variações:

    • commandinput_file > temp_file && cp temp_file input_file && rm temp_file
      que ainda (potencialmente) deixará o temp_filemundo legível. Melhor ainda:
    • cp input_file temp_file && commandtemp_file > input_file && rm temp_file
      Eles preservam o status do link, o proprietário e o modo (proteção) do arquivo, potencialmente ao custo de duas vezes mais E / S. (Você pode precisar usar uma opção como -aou -pativada cp para dizer a ela para preservar atributos.)
    • commandinput_file > temp_file &&
      cp --attributes-only --preserve=all input_file temp_file &&
      mv temp_file input_file
      (dividido em linhas separadas apenas para facilitar a leitura) Isso preserva o modo do arquivo (e, se você é root, o proprietário), mas o torna propriedade de você (se você não é root) e o torna um novo, arquivo separado.
  • Este blog (edição "in-loco" de arquivos) sugere e explica

    {rm input_file   &&   command …> input_file ; } < arquivo_de_ entrada

    Isso requer que commandseja capaz de processar a entrada padrão (mas quase todos os filtros podem). O próprio blog chama isso de kludge arriscado e desencoraja seu uso. E isso também criará um novo arquivo separado (não vinculado a nada), de sua propriedade e com permissões padrão.

  • O pacote moreutils possui um comando chamado sponge:

    comandoarquivo_de_ entrada | esponja the_same_file

    Veja esta resposta para mais informações.

Aqui está uma coisa que me surpreendeu completamente: o syntaxerror diz :

[A maioria dessas soluções] falhará em um sistema de arquivos somente leitura, onde “somente leitura” significa que você $HOME será gravável, mas /tmpserá somente leitura (por padrão). Por exemplo, se você possui o Ubuntu e inicializou no Console de recuperação, esse é geralmente o caso. Além disso, o operador here-document <<<não vai funcionar lá também, uma vez que requer /tmpa ser de leitura / gravação , porque ele vai escrever um arquivo temporário para lá também.
(cf. esta pergunta inclui uma stracesaída 'd)

O seguinte pode funcionar nesse caso:

  • Somente para usuários avançados: Se for garantido que seu comando produz a mesma quantidade de dados de saída que há de entrada (por exemplo,, sortou tr sem a opção -dou -s), você pode tentar
    comandoarquivo_de_ entrada | dd of = nome_do_arquivo conv = notrunc
    Consulte esta resposta e esta resposta para obter mais informações, incluindo uma explicação do exposto acima, e alternativas que funcionem se for garantido que seu comando produz a mesma quantidade de dados de saída que há entrada ou menos (por exemplo,, grepou cut). Essas respostas têm a vantagem de não exigir espaço livre (ou exigir muito pouco). As respostas acima do formulário exigem claramente que haja espaço livre suficiente para o sistema poder manter o arquivo inteiro de entrada (antigo) e o arquivo de saída (novo) simultaneamente; isso não é obviamente verdade para a maioria das outras soluções (por exemplo, e ) também. Exceção: provavelmente exigirá muito espaço livre, porquecommandinput_file > temp_file && …sed -ispongesort … | dd …sort precisa ler todas as suas entradas antes que possa gravar qualquer saída e provavelmente armazena em buffer a maioria dos dados, se não todos, em um arquivo temporário.
  • Apenas para usuários avançados:
    comandoarquivo_de_ entrada 1 <> arquivo_do_ame
    pode ser equivalente à ddresposta acima. A sintaxe abre o arquivo nomeado no descritor de arquivos para entrada e saída , sem truncá-lo - uma espécie de combinação de e . Nota: Alguns programas (por exemplo, e ) podem se recusar a executar neste cenário porque podem detectar que a entrada e a saída são o mesmo arquivo. Consulte esta resposta para uma discussão sobre o exposto acima e um script que faça essa resposta funcionar se for garantido que seu comando produz a mesma quantidade de dados de saída que há entrada ou menos . Aviso: eu não testei o script de Peter, por isso não atesto.n<> filen n<n>catgrep

Então, qual foi a pergunta?

Este tem sido um tópico popular em U&L; é abordado nas seguintes perguntas:

... e isso não está contando como Superusuário ou Ask Ubuntu. Eu incorporei muitas informações das respostas às perguntas acima aqui nesta resposta, mas não todas. (Ou seja, para obter mais informações, leia as perguntas listadas acima e suas respostas.)

PS Eu tenho nenhuma afiliação com o blog que citei acima.

Scott
fonte
Como essa pergunta continua surgindo, pensei em escrever uma “resposta canônica”. Devo publicá-lo aqui (e talvez vinculá-lo a partir de outras perguntas com mais tráfego) ou devo movê-lo para uma das perguntas que realmente levanta esse problema? Além disso, essa talvez seja uma situação em que as perguntas devem ser mescladas?
Scott
/ tmp Um diretório disponibilizado para aplicativos que precisam de um local para criar arquivos temporários. Os aplicativos devem criar arquivos neste diretório, mas não devem assumir que esses arquivos são preservados entre as invocações do aplicativo.
mikeserv
@mikeserv: Sim, (1) estou citando um erro de sintaxe e (2) disse que fiquei surpreso. Eu pensei que, se alguma coisa fosse ler-escrever, seria /tmp.
Scott
Bem, o que o @syntaxerror disse é duplamente estranho, porque, como eu acho, dashseria o shell de recuperação padrão no Ubuntu e não apenas não entende um <<<herestring, mas também obtém tubos anônimos para <<heredocuments e não interfere ${TMPDIR:-/tmp}nisso. propósito em tudo. Veja isto ou isto para obter demonstrações sobre o manuseio de documentos aqui. Também por que a mesma quantidade de saída ou menos aviso?
mikeserv
@ mikeserv: Bem, as dd … conv=notrunce as 1<>respostas nunca truncam o arquivo de saída; portanto, se a saída do comando for menor que a entrada (por exemplo, grep), haverá alguns bytes do original restante no final do arquivo. E, se a saída é maior que o de entrada (por exemplo, cat -n, nlou (potencialmente) grep -n), há um risco de sobrescrever os dados antigos antes que você lê-lo.
Scott
29

Mais observações sobre ;, &, (e)

  • Observe que alguns dos comandos na resposta de terdon podem ser nulos. Por exemplo, você pode dizer

    command1 ;

    (sem command2). Isso é equivalente a

    command1

    (ou seja, ele simplesmente roda command1em primeiro plano e aguarda a conclusão. Comparativamente,

    command1 &

    (sem command2) será iniciado command1em segundo plano e emitirá outro prompt de shell imediatamente.

  • Em contraste, command1 &&, command1 ||, e command1 |não faz qualquer sentido. Se você digitar um destes, o shell (provavelmente) assumirá que o comando continua em outra linha. Ele exibirá o prompt do shell secundário (continuação), que normalmente é definido como >e continua lendo. Em um script de shell, ele apenas lerá a próxima linha e anexará ao que já foi lido. (Cuidado: isso pode não ser o que você deseja que aconteça.)

    Nota: algumas versões de alguns shells podem tratar comandos incompletos como erros. Nesses casos (ou, de fato, em qualquer caso em que você tenha um comando longo), você pode colocar uma barra invertida ( \) no final de uma linha para dizer ao shell para continuar lendo o comando em outra linha:

    command1  &&  \
    command2

    ou

    find starting-directory -mindepth 3 -maxdepth 5 -iname "*.some_extension" -type f \
                            -newer some_existing_file -user fred -readable -print
  • Como diz o terdon, (e )pode ser usado para agrupar comandos. A afirmação de que eles “não são realmente relevantes” para essa discussão é discutível. Alguns dos comandos na resposta de terdon podem ser grupos de comandos . Por exemplo,

    ( command1 ; command2 )  &&  ( command3; command4 )

    faz isso:

    • Execute command1e aguarde o término.
    • Em seguida, independentemente do resultado da execução do primeiro comando, execute command2e aguarde o término.
    • Então, se for command2bem sucedido,

      • Execute command3e aguarde o término.
      • Em seguida, independentemente do resultado da execução desse comando, execute command4e aguarde o término.

      Se command2falhar, pare de processar a linha de comandos.

  • Parênteses externos, |se unem muito firmemente,

    command1 | command2 || command3

    é equivalente a

    ( command1 | command2 )  ||  command3

    e &&e ||ligam-se mais apertada do que ;, por isso

    command1 && command2 ; command3

    é equivalente a

    ( command1 && command2 ) ;  command3

    ou seja, command3será executado independentemente do status de saída de command1e / ou command2.

G-Man
fonte
Perfeito, +1! Eu disse que eles não eram relevantes porque não queria entrar em tantos detalhes. Eu queria uma resposta que pudesse funcionar como uma folha de dicas rápidas para iniciantes que estão se perguntando o que são todos os rabiscos estranhos no final dos vários comandos. Eu não quis dizer que elas não são úteis. Obrigado por adicionar tudo isso.
terdon
1
Estou preocupado com o problema da "massa crítica" - se publicarmos tudo o que poderíamos dizer sobre os shells, terminaremos com nossa própria versão TL; DR do Manual de Referência do Bash.
G-Man
Também vale a pena mencionar: Ao contrário dos idiomas da família C, ;por si só (ou sem um comando anterior), há um erro de sintaxe, e não uma declaração vazia. Assim ; ;é um erro. (Uma armadilha comum para novos usuários, IMHO). Também: ;;é um delimitador especial, para caseinstruções.
Muru
1
@ muru: Bom ponto, mas vamos generalizá-lo. Qualquer dos operadores de controle que podem aparecer entre os comandos: ;, &&, ||, &, e |, são erros que eles parecem com nada que os precedem. Terdon também abordou ;;(brevemente) em sua resposta.
G-Man
1
@Wildcard: OK, vejo de onde você vem. A palavra-chave é "maio"; tudo o que eu estava dizendo era que não garanto que todas as conchas aceitem tais construções (ie, YMMV). Obviamente, escrevi isso antes de saber sobre o uso do linebreaktoken na gramática do shell POSIX. Portanto, talvez seja seguro dizer que todos os shells compatíveis com POSIX os aceitarão. Eu mantenho minha declaração como um aviso geral; se você encontrar um shell pré-POSIX antigo o suficiente, como um shell Bourne real ou mais antigo, todas as apostas serão desativadas.
G-Man