O que determina como os argumentos do programa precisam ser separados?

2

Percebi que kill utility é capaz de aceitar argumentos separados por um caractere de espaço e uma nova linha:

$ sleep 120 & sleep 240 &
[1] 75341
[2] 75342
$ /bin/kill $(echo "75341 75342")
[1]-  Terminated: 15          sleep 120
[2]+  Terminated: 15          sleep 240
$ sleep 120 & sleep 240 &
[1] 75577
[2] 75578
$ /bin/kill $(echo -e "75577\n75578")
[1]-  Terminated: 15          sleep 120
[2]+  Terminated: 15          sleep 240
$ 

Estou certo de que isso depende apenas da própria utilidade se ela aceita argumentos, por exemplo, separados por uma nova linha?

    
por Martin 12.11.2015 / 14:16

5 respostas

1

Os argumentos para um utilitário são passados como uma lista de strings , não como uma única string. Portanto, o utilitário não tem noção de separador entre argumentos. Os argumentos são apenas elementos separados na lista, não há nada entre eles.

A entidade que divide uma string em uma lista de argumentos é o shell. O shell executa a linha de comando como /bin/kill $(echo "75341 75342") executando uma série de expansões . Especificamente:

  1. O comando é dividido em tokens. Eu não vou entrar nessas regras em detalhes; um token é basicamente um sinal de pontuação ou uma sequência de caracteres que não contém espaços em branco de nível superior. Aqui, os tokens são a string /bin/kill e a substituição do comando $(echo "75341 75342") , que é criada a partir do operador $(…) e dos tokens echo e 75341 75342 .
  2. O comando no operador de substituição de comandos é executado. É um comando simples, com o nome do comando echo e o único argumento 75341 75342 . (As aspas fazem parte da sintaxe do shell, elas delimitam uma string, que se torna um argumento para o comando).
  3. A saída do comando é 75341 75342␤ , em que é um caractere de nova linha. O shell pega essa saída e remove as novas linhas finais, gerando a string 75341 75342 .
  4. Como o operador de substituição de comandos é usado em um contexto de lista (fora de aspas duplas), ele passa por divisão de palavras e expansão de nome de arquivo . A divisão de palavras consiste em pegar a string e dividi-la em uma lista de strings com base no valor da variável IFS . Por padrão, IFS contém os caracteres space, tab e newline, portanto, a string é dividida em qualquer sequência desses caracteres: ela se torna a lista de duas strings 75341 e 75342 . A expansão do nome de arquivo não altera nada aqui.
  5. Agora temos uma lista de três strings: /bin/kill , 75341 e 75342 . Isso é executado como o comando /bin/kill com dois argumentos 75341 e 75342 .

Com o comando /bin/kill $(echo -e "75577\n75578") , as expansões são praticamente as mesmas. O passo 3 produz a saída 75341␤75342␤ . Na etapa 4, a divisão de palavras produz a mesma lista 75341 , 75342 como antes, porque uma nova linha e um espaço são separadores de palavras igualmente válidos. Assim, o passo 5 executa exatamente o mesmo comando.

Como você pode ver, a etapa que determina o que separa os argumentos é a etapa de divisão de palavras executada pelo shell. Você pode experimentar essa etapa alterando o valor de IFS . Por exemplo, isso produz novamente o mesmo comando:

IFS=+
/bin/kill $(echo "75341+75342")
    
por 13.11.2015 / 01:15
2

Como você não cita $(echo -e "75577\n75578") , cabe ao shell processá-lo.

A maneira como o shell analisa o que ele obtém depende de uma variável chamada IFS (Internal Field Separator). Por padrão, ele contém espaço, tabulação e nova linha, o que significa que qualquer combinação desses três caracteres é considerada um separador válido entre os argumentos.

Se você configurá-lo para uma string que não contenha nova linha, seu comando receberá o \n incorporado ao argumento e falhará:

$ touch a b
$ echo "$IFS" | od -c
0000000      \t  \n  \n
0000004$ 
$ ls -l a b
-rw-r--r-- 1 jlliagre jlliagre 0 Nov 12 15:04 a
-rw-r--r-- 1 jlliagre jlliagre 0 Nov 12 15:04 b
$ ls -l $(printf "a\nb")
-rw-r--r-- 1 jlliagre jlliagre 0 Nov 12 15:05 a
-rw-r--r-- 1 jlliagre jlliagre 0 Nov 12 15:05 b
$ IFS=" "
$ ls -l $(printf "a\nb")
ls: cannot access a
b: No such file or directory
$
    
por 12.11.2015 / 15:07
1

Acho que é a construção $( ) que substitui a nova linha por espaço. Apenas use echo $(echo -e "75577\n75578") .

Agora tente

cat <<EOF | kill
1234
5678
EOF

(com valor adequado).

    
por 12.11.2015 / 14:43
0

Não, algo está substituindo a nova linha.

% sleep 120 & sleep 120 &
15362
15363

% strace -f -eexecve /bin/kill $(echo "15362 15363")
execve("/bin/kill", ["/bin/kill", "15362", "15363"], [/* 18 vars */]) = 0

% strace -f -eexecve /bin/kill $(echo -e "15362\n15363")
execve("/bin/kill", ["/bin/kill", "15362", "15363"], [/* 18 vars */]) = 0
kill: can't kill pid 15362: No such process
...

Use strace (1) para determinar como a linha de comando é passada aos seus processos criados.

    
por 12.11.2015 / 14:43
0

Não é exatamente sobre kill , mesmo com (ou seja) ls você terá esse comportamento:

> touch file1 file2
> ls $(echo -e "file1 file2")
file1 file2
> ls $(echo -e "file1\nfile2")
file1 file2

Isso porque não é sobre o comando em si, mas sobre como o bash lida com scripts. Será mais claro o que quero dizer se você olhar para isso do lado do 'script'. Tente redirecionar o comando dentro de um arquivo, chmod-lo para 777 e execute:

> touch file1 file2
> ls $(echo -e "file1\nfile2")
file1 file2
> echo -e 'ls $(echo -e "file1\nfile2")' > tryme.sh
> cat tryme.sh
ls $(echo -e "file1
file2")
> chmod 777 tryme.sh
> ./tryme.sh
file1 file2
    
por 12.11.2015 / 14:56