Qual é a diferença entre <<, <<< e <<no bash?

103

Qual é a diferença entre <<, <<<e < <no bash?

Searene
fonte
20
Pelo menos nesses tempos centrados no Google, é difícil procurar esses operadores baseados em símbolos. Existe um mecanismo de pesquisa onde você pode conectar "<< <<< <<" e obter alguma coisa útil?
Daniel Griscom
11
@DanielGriscom Há SymbolHound .
Dennis
1
O @DanielGriscom Stack Exchange costumava oferecer suporte à pesquisa de símbolos, mas então algo quebrou e ninguém nunca o corrigiu.
muru
Já está lá (e já existe há quase um ano): Quais são os operadores de controle e redirecionamento do shell?
Scott Scott

Respostas:

116

Aqui documento

<<é conhecido como here-documentestrutura. Você deixa o programa saber qual será o texto final e, sempre que esse delimitador for visto, ele lerá todas as coisas que você deu ao programa como entrada e executará uma tarefa nele.

Aqui está o que eu quero dizer:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

Neste exemplo, dizemos wcao programa para aguardar a EOFstring, digite cinco palavras e depois digita EOFpara sinalizar que estamos dando entrada. Na verdade, é semelhante a correr wcsozinho, digitando palavras e pressionandoCtrlD

No bash, eles são implementados por meio de arquivos temporários, geralmente no formato /tmp/sh-thd.<random string>, enquanto no painel são implementados como pipes anônimos. Isso pode ser observado através do rastreamento de chamadas do sistema com o stracecomando Substitua bashpor shpara ver como /bin/shexecuta esse redirecionamento.

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'

Here string

<<<é conhecido como here-string. Em vez de digitar texto, você fornece uma sequência de texto pré-criada para um programa. Por exemplo, com o programa bcque podemos fazer bc <<< 5*4para obter saída para esse caso específico, não é necessário executar o bc interativamente.

As strings here no bash são implementadas por meio de arquivos temporários, geralmente no formato /tmp/sh-thd.<random string>, que são desvinculados posteriormente, fazendo com que ocupem algum espaço de memória temporariamente, mas não apareçam na lista de /tmpentradas do diretório, e existam efetivamente como arquivos anônimos, que ainda podem ser referenciado por meio do descritor de arquivo pelo próprio shell e esse descritor de arquivo ser herdado pelo comando e posteriormente duplicado no descritor de arquivo 0 (stdin) via dup2()função. Isso pode ser observado via

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd

E via rastreamento syscalls (saída reduzida para facilitar a leitura; observe como o arquivo temporário é aberto como fd 3, dados gravados nele, em seguida, é reaberto com o O_RDONLYsinalizador como fd 4 e depois desvinculado, depois dup2()para o fd 0, que é herdado catposteriormente. ):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Opinião: potencialmente porque as strings here usam arquivos de texto temporários, é a possível razão pela qual as strings here sempre inserem uma nova linha à direita, já que o arquivo de texto por definição POSIX precisa ter linhas que terminam em caracteres de nova linha.

Substituição de Processo

Como tldp.org explica,

A substituição de processo alimenta a saída de um processo (ou processos) no padrão de outro processo.

Portanto, na verdade, isso é semelhante ao canal stdout de um comando para o outro, por exemplo echo foobar barfoo | wc. Mas observe: na página de manual do bash, você verá que é indicado como <(list). Então, basicamente, você pode redirecionar a saída de vários comandos (!).

Nota: tecnicamente quando você diz < <que não está se referindo a uma coisa, mas dois redirecionamentos com redirecionamento único <e de processo de saída de <( . . .).

Agora, o que acontece se processarmos apenas a substituição?

$ echo <(echo bar)
/dev/fd/63

Como você pode ver, o shell cria um descritor temporário de arquivo para /dev/fd/63onde vai a saída (que, de acordo com a resposta de Gilles , é um canal anônimo). Isso significa <que redireciona esse descritor de arquivo como entrada para um comando.

Um exemplo muito simples seria fazer a substituição do processo da saída de dois comandos echo no wc:

$ wc < <(echo bar;echo foo)
      2       2       8

Então, aqui fazemos o shell criar um descritor de arquivo para toda a saída que ocorre entre parênteses e redirecioná-la como entrada para wc. Como esperado, o wc recebe esse fluxo de dois comandos de eco, que por si só produziriam duas linhas, cada uma com uma palavra, e apropriadamente, temos 2 palavras, 2 linhas e 6 caracteres mais duas novas linhas contadas.

Nota: A substituição de processo pode ser chamada de bashismo (um comando ou estrutura utilizável em shells avançados como bash, mas não especificado pelo POSIX), mas foi implementada kshantes da existência do bash como página de manual do ksh e esta resposta sugere. Cascas como tcshe mkshno entanto não têm substituição de processo. Então, como podemos redirecionar a saída de vários comandos para outro comando sem substituição de processo? Agrupamento e tubulação!

$ (echo foo;echo bar) | wc
      2       2       8

Efetivamente, é o mesmo do exemplo acima. No entanto, isso é diferente sob a cobertura da substituição do processo, uma vez que fazemos stdout de todo o subshell e stdin wc vinculados ao pipe . Por outro lado, a substituição do processo faz com que um comando leia um descritor de arquivo temporário.

Portanto, se podemos agrupar com tubulação, por que precisamos de substituição de processo? Porque às vezes não podemos usar tubulações. Considere o exemplo abaixo - comparando saídas de dois comandos com diff(que precisa de dois arquivos e, neste caso, estamos fornecendo dois descritores de arquivos)

diff <(ls /bin) <(ls /usr/bin)
Sergiy Kolodyazhnyy
fonte
7
< <é usado quando alguém está obtendo stdin de uma substituição de processo . Tal comando pode parecer: cmd1 < <(cmd2). Por exemplo,wc < <(date)
John1024
4
Sim, a substituição do processo é suportada pelo bash, zsh e AT&T ksh {88,93}, mas não pelo pdksh / mksh.
precisa saber é o seguinte
2
< < não é uma coisa por si só, no caso de substituição de processo, é apenas um <seguido por outra coisa que acontece no início<
user253751
1
@muru Até onde eu sei, <<<foi implementado pela porta Unix do Plan 9 rc shell e depois adotado pelo zsh, bash e ksh93. Eu não chamaria isso de basismo.
Jlliagre
3
Outro exemplo de onde a tubulação não pode ser usado: echo 'foo' | read; echo ${REPLY}vai não voltar foo, porque readé iniciado em uma sub-shell - tubulação inicia uma sub-shell. No entanto, read < <(echo 'foo'); echo ${REPLY}retorna corretamente foo, porque não há subcasca.
Paddy Landau
26

< < é um erro de sintaxe:

$ cat < <
bash: syntax error near unexpected token `<'

< <()é a substituição do processo ( <()) combinada com o redirecionamento ( <):

Um exemplo artificial:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63

Com a substituição do processo, o caminho para o descritor de arquivo é usado como um nome de arquivo. Caso você não queira (ou não possa) usar um nome de arquivo diretamente, combine a substituição do processo com o redirecionamento.

Para ser claro, não há < <operador.

muru
fonte
Eu recebo pela sua resposta que, <<() é mais útil que <(), certo?
solfish
1
O @solfish <()fornece uma coisa parecida com o nome do arquivo, por isso é geralmente mais útil - < <()está substituindo o stdin onde pode não ser necessário. Em wc, o último passa a ser mais útil. Pode ser menos útil em outro lugar
muru
12

< <é um erro de sintaxe, você provavelmente quer dizer command1 < <( command2 )que é um redirecionamento simples de entrada seguido por uma substituição de processo e é muito semelhante, mas não equivalente a:

command2 | command1

A diferença assumindo que você está executando bashé command1executada em um subshell no segundo caso, enquanto é executada no shell atual no primeiro. Isso significa que as variáveis ​​definidas command1não seriam perdidas com a variante de substituição do processo.

jlliagre
fonte
11

< <dará um erro de sintaxe. O uso adequado é o seguinte:

Explicando com a ajuda de exemplos:

Exemplo para < <():

while read line;do
   echo $line
done< <(ls)

No exemplo acima, a entrada para o loop while virá do lscomando que pode ser lido linha por linha e echoed no loop.

<()é usado para substituição de processo. Mais informações e exemplos <()podem ser encontrados neste link:

Substituição de processo e tubulação

bisbilhoteiro
fonte