Como são aspas duplas no bash correspondido (emparelhado)?

15

Estou usando GNU bash 4.3.48 . Considere os dois comandos seguintes que diferem apenas por um único sinal de dólar.

Comando 1:

echo "(echo " * ")"

Comando 2:

echo "$(echo " * ")"

A saída deles é, respectivamente

(echo  test.txt ppcg.sh )

e

 * 

Então, obviamente, no primeiro caso, o * é globbed, o que significa que a primeira aspa vai com a segunda para formar um par, e a terceira e a quarta formam outro par.

No segundo caso, o * não é globbed, e há exatamente dois espaços extras na saída, um antes do asterisco e outro depois, o que significa que a segunda aspa vai com a terceira, e a primeira vai com o quarto.

Existem outros casos além da construção $() em que as aspas não coincidem com a próxima, mas estão aninhados? Esse comportamento é bem documentado e, se sim, onde posso encontrar o documento correspondente?

    
por Weijun Zhou 04.02.2018 / 01:49

2 respostas

14

Qualquer uma das construções de aninhamento que podem ser interpoladas dentro de strings pode ter outras strings dentro delas: elas são analisadas como um novo script, até o marcador de fechamento e podem até ser aninhadas em vários níveis de profundidade. Todas as barras uma daquelas começam com $ . Todos eles são documentados em uma combinação do manual do Bash e da especificação da linguagem de comandos shell POSIX.

Existem alguns casos dessas construções:

  • Substituição de comandos com $( ... ) , como você encontrou. POSIX especifica esse comportamento :

    With the $(command) form, all characters following the open parenthesis to the matching closing parenthesis constitute the command. Any valid shell script can be used for command ...

    As citações fazem parte de scripts de shell válidos, então elas são permitidas com o significado normal.

  • Substituição de comando usando ' também.
  • O elemento "palavra" de instâncias avançadas de substituição de parâmetros, como ${parameter:-word} . A definição de "palavra" é :

    A sequence of characters treated as a unit by the shell

    - que inclui texto citado e até mesmo aspas mistas a"b"c'd'e - embora o comportamento real das expansões seja um pouco mais liberal do que isso, e por exemplo ${x:-hello world} também funciona.

  • Expansão aritmética com $(( ... )) , embora seja em grande parte inútil (mas você também pode aninhar substituições de comandos ou expansões de variáveis, e então ter aspas úteis dentro delas). POSIX afirma que :

    The expression shall be treated as if it were in double-quotes, except that a double-quote inside the expression is not treated specially. The shell shall expand all tokens in the expression for parameter expansion, command substitution, and quote removal.

    , então esse comportamento é explicitamente exigido. Isso significa que echo "abc $((4 "*" 5))" faz aritmética, em vez de globbing.

    Note que a expansão aritmética em $[ ... ] do estilo antigo não é tratada da mesma maneira: aspas serão um erro se aparecerem, independentemente de a expansão estar ou não entre aspas. Este formulário não está mais documentado e não deve ser usado de qualquer maneira.

  • Tradução específica de local com $"..." , que realmente usa o " como um elemento central. $" é tratado como uma única unidade.

Há mais um caso de aninhamento que você não pode esperar, não envolvendo cotações, que é com expansão da cinta : {a,b{c,d},e} expande para "a bc bd e". ${x:-a{b,c}d} não não aninha, no entanto; Ele é tratado como uma substituição de parâmetro que fornece " a{b,c ", seguido por " d} ". O também está documentado :

When braces are used, the matching ending brace is the first ‘}’ not escaped by a backslash or within a quoted string, and not within an embedded arithmetic expansion, command substitution, or parameter expansion.

Como regra geral, todas as construções delimitadas analisam seus corpos independentemente do contexto circundante (e exceções são tratadas como bugs ). Em essência, ao ver $( , o código de substituição de comando apenas solicita ao analisador que consuma o que pode do corpo como se fosse um novo programa e, em seguida, verifica se o marcador de finalização esperado (uma ) ou )) sem escape ou } ) aparece quando o sub-analisador fica sem coisas que pode consumir.

Se você pensar sobre o funcionamento de um analisador de descendência recursiva , isso é apenas uma simples recursão ao caso base. É realmente mais fácil de fazer do que o contrário, uma vez que você tenha interpolação de string. Independentemente da técnica de análise subjacente, os shells que suportam essas construções fornecem o mesmo resultado.

Você pode aninhar aspas tão profundamente quanto quiser por meio dessas construções e funcionará conforme o esperado. Em nenhum lugar vai ficar confuso por ver uma citação no meio; em vez disso, esse será o início de uma nova string entre aspas no contexto interno.

    
por 04.02.2018 / 02:49
3

Talvez olhar os dois exemplos com printf (em vez de echo ) ajudará:

$ printf '<%s> ' "(echo " * ")"; echo
<(echo > <test.txt> <ppcg.sh> <file1> <file2> <file3> <)>

Imprime (echo  (a primeira palavra, incluindo um espaço à direita), alguns arquivos e a palavra de fechamento ) .
O parêntese é apenas parte da string citada (echo  .
O asterisco (agora sem aspas, como as duas aspas duplas são emparelhadas) é expandido como um glob para uma lista de arquivos correspondentes.
E então, o parêntese de fechamento.

No entanto, seu segundo comando funciona da seguinte maneira:

$ printf '<%s> ' "$(echo " * ")" ; echo
< * >

O $ inicia uma substituição de comando. Isso inicia uma nova citação.
O asterisco é citado " * " e é isso que o comando (aqui é um comando e não uma string entre aspas) echo outputs. Finalmente, printf re-formata o * e o imprime como < * > .

    
por 04.02.2018 / 05:22