Por que o bash adiciona aspas simples às expansões de nome de caminho com falha sem nome em um comando antes de executá-lo?

5

Eu estava explorando o rastreamento de comandos usando set -x ( set +x para não definido) em bash :

Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands and their arguments or associated word lists after they are expanded and before they are executed. The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.

Agora, considere o seguinte, rastreando o uso do bash echo \[-neE\] \[arg …\] comando com e sem citações:

# set -x        # what I typed
# echo 'love'   # ...
+ echo love     <--(1) the trace
love            # the output

# echo love?    # note the input contains no quote whatsoever
+ echo 'love?'  <--(2) note the trace contains quotes after returning word
love?           # i.e. failed to find any file 

# echo 'love?'  # note the input contains single quotes
+ echo 'love?'  <--(3) traces like (2)
love?

# touch loveu   # we create a file that matches the love? pattern
+ touch loveu

# echo love?    # of course now, the pattern matches the created file now
+ echo loveu    <--(4) indeed it finds it and expands to name
loveu           # the name is echoed

Portanto, ? é realmente interpretado neste caso como um caractere especial usado para correspondência de padrões um único caractere na expansão do nome do caminho . Com certeza, uma vez que um arquivo correspondente ao padrão foi criado no diretório atual, a correspondência ocorreu e o nome do arquivo foi impresso. É claro que esse comportamento é documentado :

If no matching file names are found, and the shell option nullglob is disabled, the word is left unchanged.

Mas o problema é que a palavra em (2) não tem aspas love? não 'love?' . O rastreamento mostra o estado antes da execução do comando , mas após expansão, e como estamos vendo há expansão de nome de caminho por causa de ? e não houve correspondências no primeiro caso (2) usamos o caractere especial. Então, as citações simples aparecem nesse caso, assim como quando usamos aspas simples (3) com a mesma string? Considerando que nos outros casos houve um literal ou a correspondência foi encontrada e, consequentemente, "substituiu" o padrão no comando. Este parece ser o que se entende na seção do manual sobre remoção de cotações logo após expansão:

After the preceding expansions, all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed. (my italics)

Então aqui (2) temos ocorrências não citadas de ' que resultam da expansão anterior. Eu não os coloquei lá; o bash fez, e agora eles não foram removidos - e estamos um pouco antes da execução do comando.

Ilustração semelhante com for

Considere essa lista usada em for name [ [in [words …] ] ; ] do commands; done loop 1 , sem arquivo correspondente:

# for i in love love? 'love?'; do echo $i; done
+ for i in love 'love?' ''\''love?'\'''
+ echo love
love
+ for i in love 'love?' ''\''love?'\'''
+ echo 'love?'
love?
+ for i in love 'love?' ''\''love?'\'''
+ echo 'love?'
love?

Portanto, o comportamento do comando echo é o mesmo, mas no caso dos itens na construção for , parece que ele está tentando ... escapar sozinho citando minhas citações ?? Eu não tenho certeza ...

Perguntas

  • Por que um padrão de expansão de nome de caminho com falha não é indicado com single quotes no contexto (2); expansão é concluída de qualquer maneira e vamos executar? Novamente, já concluímos a expansão e o padrão falhou - nada deveria ter que expandir mais. Eu acho que o que eu estou perguntando é por que nos importamos neste momento - o ponto em que estamos é antes de 3.7.2-4 no manual bash. Por que isso não é deixado "como está" e a expansão é simplesmente desativada para a execução de comandos, por exemplo, algo como set -f ?
  • (O que o loop for está fazendo com meu único item citado na lista?)

1. Ao usar uma tal lista de palavras com for, é realmente uma lista de itens e os valores são para conveniência realmente como eu acho t="0"; for i in 0 0 0 0; do let t++; echo "yes, this is really $t times"; done bastante convincente.

    
por jus cogens prime 06.03.2014 / 13:37

2 respostas

10

Quando instruídos a ecoar os comandos à medida que são executados ("trace de execução"), bash e ksh adicionam aspas simples em torno de qualquer palavra com meta-caracteres ( * , ? , ; , etc.) nele.

Os metacaracteres poderiam ter entrado na palavra de várias formas. A palavra (ou parte dela) poderia ter sido citada com aspas simples ou duplas, os caracteres poderiam ter escapado com um \ ou permaneceram como resultado de uma tentativa de correspondência de nome de arquivo com falha. Em todos os casos, o rastreio de execução conterá palavras com aspas simples, por exemplo:

$ set -x
$ echo foo\;bar
+ echo 'foo;bar'

Este é apenas um artefato do modo como os shells implementam o rastreio de execução; isso não altera a maneira como os argumentos são finalmente passados para o comando. As aspas são adicionadas, impressas e descartadas. Aqui está a parte relevante do código-fonte bash , print_cmd.c :

/* A function to print the words of a simple command when set -x is on. */
void
xtrace_print_word_list (list, xtflags)
...
{
  ...
  for (w = list; w; w = w->next)
    {
      t = w->word->word;
      ...
      else if (sh_contains_shell_metas (t))
        {
          x = sh_single_quote (t);
          fprintf (xtrace_fp, "%s%s", x, w->next ? " " : "");
          free (x);
        }

Por que os autores escolheram fazer isso, o código não diz. Mas aqui está um código semelhante em variables.c , e vem com um comentário:

/* Print the value cell of VAR, a shell variable.  Do not print
   the name, nor leading/trailing newline.  If QUOTE is non-zero,
   and the value contains shell metacharacters, quote the value
   in such a way that it can be read back in. */
void
print_var_value (var, quote)
...
{
  ...
  else if (quote && sh_contains_shell_metas (value_cell (var)))
    {
      t = sh_single_quote (value_cell (var));
      printf ("%s", t);
      free (t);
    }

Assim, é possível que seja mais fácil copiar as linhas de comando da saída do rastreio de execução e executá-las novamente.

    
por 06.03.2014 / 18:04
1

Porque os apóstrofos internos ("aspas simples") estão definidos para não fazer expansões, enquanto expansões internas são executadas ... verifique seu manual com cuidado.

A globalização de concha expande determinados caracteres, ou seja, * e ? . Se nenhum nome de arquivo corresponder, ele retornará apenas o original. Ou seja, se você tiver apenas arquivos lovers , love1 e love2 , então love? expandirá para love1 love2 , love* expandirá para love1 love2 lovers , enquanto hate? fornecerá apenas hate? . Experimente um pouco ...

    
por 06.03.2014 / 13:57