Por que o shell trata uma parte da saída de $ (file) como um comando?

3

Eu vi esta linha enquanto lia um blog no IFS isto é:

for i in $(<test.txt)

E pensei que $(<test.txt) imprime o conteúdo do arquivo em STDOUT. Eu talvez esteja errado nisso, mas por curiosidade eu tentei fazer isso na shell. Então pegou um arquivo aleatório chamado array com dados aleatórios e

Primeiro fiz um cat array que me deu isso:

amit@C0deDaedalus:~/test$ 
amit@C0deDaedalus:~/test$ cat array
1)      Ottawa  Canada          345644
2)      Kabul   Afghanistan     667345
3)      Paris   France          214423
4)      Moscow  Russia          128793
5)      Delhi   India           142894

E então $(<array) me deu isso:

amit@C0deDaedalus:~/test$ $(<array)
1)      Ottawa  Ca: command not found

Eu só sei que < é usado para redirecionamento de entrada, mas não obtendo exatamente o que está sendo interpretado pelo shell como um comando aqui.

Alguém pode explicar o conceito por trás dessa saída estranha no shell?

Atualização: -

Na execução de set -x , é exibido:

amit@C0deDaedalus:~/test$ $(<array)
+ '1)' Ottawa Canada 345644 '2)' Kabul Afghanistan 667345 '3)' Paris France 214423 '4)' Moscow Russia 128793 '5)' Delhi India 142894
+ '[' -x /usr/lib/command-not-found ']'
+ /usr/lib/command-not-found -- '1)'
1): command not found
+ return 127
amit@C0deDaedalus:~/test$ 
    
por C0deDaedalus 28.04.2018 / 11:50

1 resposta

17

A sintaxe $(command) executa command em um ambiente de subshell e substitui a saída padrão de command . E, como o Bash Manual diz , $(< file) é apenas um equivalente mais rápido de $(cat file) (isso não é um recurso POSIX, no entanto).

Portanto, quando você executa $(<array) , o Bash realiza essa substituição, então ele usa o primeiro campo como o nome do comando e o restante dos campos como argumentos do comando:

$ $(<array)
1): command not found

Eu não tenho nenhum comando / função 1) , então ele imprime uma mensagem de erro.

Mas, no seu cenário específico, você está recebendo uma mensagem de erro diferente, provavelmente porque você modificou a variável IFS:

$ IFS=n; $(<array)
1)      Ottawa  Ca: command not found

Editar 1

Meu palpite é que seu IFS foi modificado de alguma forma, por isso seu shell tentou executar 1) Ottawa Ca em vez de 1) . Afinal, você estava lendo um artigo relacionado a IFS . Eu não ficaria surpreso se o seu IFS tivesse um valor estranho.

A variável IFS controla o que é conhecido como divisão de palavras ou divisão de campos . Ele basicamente define como os dados serão analisados pelo shell em um contexto de expansão (ou por outros comandos como read ).

Manual do Bash explica este tópico :

3.5.7 Word Splitting

The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting.

The shell treats each character of $IFS as a delimiter, and splits the results of the other expansions into words using these characters as field terminators. If IFS is unset, or its value is exactly <space><tab><newline>, the default, then sequences of <space>, <tab>, and <newline> at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words. If IFS has a value other than the default, then sequences of the whitespace characters space, tab, and newline are ignored at the beginning and end of the word, as long as the whitespace character is in the value of IFS (an IFS whitespace character). Any character in IFS that is not IFS whitespace, along with any adjacent IFS whitespace characters, delimits a field. A sequence of IFS whitespace characters is also treated as a delimiter. If the value of IFS is null, no word splitting occurs.

Explicit null arguments ("" or '') are retained and passed to commands as empty strings. Unquoted implicit null arguments, resulting from the expansion of parameters that have no values, are removed. If a parameter with no value is expanded within double quotes, a null argument results and is retained and passed to a command as an empty string. When a quoted null argument appears as part of a word whose expansion is non-null, the null argument is removed. That is, the word -d'' becomes -d after word splitting and null argument removal.

Note that if no expansion occurs, no splitting is performed.

Aqui estão alguns exemplos sobre IFS e uso de substituição de comandos:

Exemplo 1:

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' ${var}
[hello]
[world]

$ IFS=$' \t\n'; var='hello     world'; printf '[%s]\n' "${var}"
[hello     world]

Em ambos os casos, IFS é <space><tab><newline> (o valor padrão), var é hello world e há uma declaração printf . Mas observe que no primeiro caso a divisão de palavras é executada, enquanto no segundo caso não é (porque aspas duplas inibem esse comportamento). A divisão de palavras ocorre em expansões não citadas.

Exemplo 2:

$ IFS='x'; var='fooxbar'; printf '[%s]\n' ${var}
[foo]
[bar]

$ IFS='2'; (exit 123); printf '[%s]\n' ${?}
[1]
[3]

Nem ${var} nem ${?} contêm qualquer caractere de espaço em branco, portanto, pode-se pensar que a divisão de palavras não seria um problema nesses casos. Mas isso não é verdade porque IFS pode ser abusado. IFS pode conter praticamente qualquer valor e é fácil abusar.

Exemplo 3:

$ $(echo uname)
Linux

$ $(xxd -p -r <<< 64617465202d75)
Sat Apr 28 12:46:49 UTC 2018

$ var='echo foo; echo bar'; eval "$(echo "${var}")"
foo
bar

Isso não tem nada a ver com a divisão de palavras, mas observe como podemos usar alguns truques sujos para injetar código.

Perguntas relacionadas:

por 28.04.2018 / 12:24