Qual é a maneira correta de citar $ (command $ arg)?

17

Está na hora de resolver esse enigma que me incomoda há anos ...

Venho encontrando isso de vez em quando e pensei que este é o caminho a seguir:

$(comm "$(arg)")

E pensei que minha visão era fortemente apoiada pela experiência. Mas não tenho mais tanta certeza. O Shellcheck também não consegue se decidir. São os dois:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

E:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Qual é a lógica por trás?

(É o Shellcheck versão 0.4.4, btw.)

Tomasz
fonte
1
Cite todas as substituições.
Ignacio Vazquez-Abrams
3
Assim "$(dirname "$0")"/stop.bash:? Parece funcionar ... Qual é a história?
Tomasz
1
o bash é inteligente o suficiente para saber quando o contexto mudou.
Ignacio Vazquez-Abrams
1
pense sobre isso da seguinte maneira:: shell_level_1 $(shell_level_2) shell_level_1quando você está dentro do, $(....)insere um "subnível" do shell, MAS você pode escrever nele como se estivesse no nível primário (ou seja, você pode escrever diretamente em "vez de \"etc). ex: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to encontre \ "/ tmp / um arquivo \" `e print \$5. Sem $(...)necessidade: o shell se adapta ao novo nível e você pode escrever diretamente como se o seu intérprete agora estivesse nesse nível também.
Olivier Dulac
"Shellcheck também não consegue se decidir." - As duas execuções têm duas mensagens diferentes e apontam para lugares diferentes. Quer os dois.
Izkata 16/05/19

Respostas:

45

Você precisa usar "$(somecmd "$file")".

Sem as aspas, um caminho com um espaço será dividido no argumento para somecmde direcionará o arquivo errado. Então você precisa de aspas no interior.

Quaisquer espaços na saída de somecmdtambém causarão divisão, portanto, você precisa de aspas na parte externa de toda a substituição do comando.

As aspas dentro da substituição de comando não têm efeito nas aspas fora dela. O manual de referência do Bash não é muito claro, mas o BashGuide menciona explicitamente . O texto no POSIX também exige isso, pois "qualquer script de shell válido" é permitido dentro $(...):

Com o $(command)formulário, todos os caracteres que seguem o parêntese aberto ao parêntese de fechamento correspondente constituem o comando. Qualquer script de shell válido pode ser usado para comando, exceto um script que consiste apenas em redirecionamentos que produzem resultados não especificados.


Exemplo:

$ file="./space here/foo"

uma. Sem aspas, dirnameprocessa ambos ./spacee here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

b. Citações dentro, dirnameprocessos ./space here/foo, doações ./space here, que são divididas em duas:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

c. As aspas externas, dirnameprocessam ambos ./spacee here/foo, são exibidas em linhas separadas, mas agora as duas linhas formam um único argumento :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

d. Cita dentro e fora, isso fornece a resposta correta:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(isso possivelmente seria mais simples se dirnameapenas processasse o primeiro argumento, mas isso não mostraria a diferença entre os casos a e c.)

Observe que com dirname(e possivelmente outros) você também precisa adicionar --, para impedir que o nome do arquivo seja usado como uma opção, caso ele comece com um traço, use-o "$(dirname -- "$file")".

ilkkachu
fonte
16
Nota: Em geral, as aspas não se aninham no bash; mas, nesse caso, $( )cria um novo contexto; portanto, as aspas dentro dele são independentes das aspas fora dele. E você geralmente deseja aspas nos dois contextos.
Gordon Davisson