A saída do script Shell é dividida incorretamente ao ser passada como argumento para o script

A saída do script Shell é dividida incorretamente ao ser passada como argumento para o script

Digamos que eu tenha os dois scripts de shell a seguir:

#!/bin/sh
#This script is named: args.sh

echo 1 "\"Two words\"" 3

, e:

#!/bin/sh
#This script is named: test.sh

echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3

Quando chamo os scripts como:

sh test.sh $(sh args.sh)

, Eu recebo:

Argument 1: 1
Argument 2: "Two
Argument 3: words"

Quando eu esperava obter:

Argument 1: 1
Argument 2: Two words
Argument 3: 3

Copiar a saída sh args.she colá-la como entrada sh test.shfunciona perfeitamente; então presumo que isso não seja realmente o que o shell está fazendo. Posso obter a saída desejada/esperada ligando sh args.sh | xargs sh test.sh.

No entanto, estou me perguntando se existe uma maneira equivalente de fazer isso sem canalizar a saída do primeiro script (args.sh) para xargs. Quero que os scripts sejam chamados na ordem original; o script de argumento para enviar os parâmetros para o segundo. Também estou procurando uma explicação de por que essa chamada não funciona conforme o esperado.

Responder1

Parte do problema é que a string retornada por args.sh não é analisada da mesma forma que um comando direto, mas apenas pelo valor de $IFS( $' \t\n'). Tente ativar o rastreamento de comando com set -x:

$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh 1 '"Two' 'words"' 3
Argument 1: 1
Argument 2: "Two
Argument 3: words"
$

Observe a linha começando com um único +: existem quatro argumentos, os '"Two'e 'words"'são dois analisados ​​como argumentos separados. O que você gostaria de fazer é alterar $IFS.

$ set -x
$ IFS='"'
+ IFS='"'
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh '1 ' 'Two words' ' 3'
Argument 1: 1
Argument 2: Two words
Argument 3:  3
$

Isso não funcionará para todas as saídas. A melhor coisa a fazer é alterar a saída de args.sh para separar a saída por algo diferente de um espaço, por exemplo, uma vírgula ou dois pontos:

$ cat /tmp/args.sh
#!/bin/sh
#This script is named: args.sh

echo "1,Two words,3"
$ IFS=,
$ sh /tmp/test.sh $(sh /tmp/args.sh)
+ sh /tmp/args.sh
+ sh /tmp/test.sh 1 Two words 3
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$

Responder2

Quando você deixa uma substituição de variável $varou de comando $(cmd)sem aspas, o resultado sofre as seguintes transformações:

  1. Divida a string resultante em palavras. A divisão acontece nos espaços em branco (sequências de espaço, tabulações e novas linhas); isso pode ser configurado por configuração IFS(veja1,2).
  2. Cada palavra resultante é tratada como um padrão glob e, se corresponder a alguns arquivos, a palavra será substituída pela lista de nomes de arquivos.

Observe que o resultado não é uma string, mas uma lista de strings. Além disso, observe que caracteres de citação como "não estão envolvidos aqui; eles fazem parte da sintaxe de origem do shell, não da expansão de strings.

Uma regra geral da programação shell é sempre colocar aspas duplas nas substituições de variáveis ​​e comandos, a menos que você saiba por que deve deixá-las desativadas. Então test.sh, escreva echo "Argument 1: $1". Para passar argumentos para test.sh, você está com problemas: você precisa passar uma lista de palavras de args.shpara test.sh, mas o método escolhido envolve uma substituição de comando e isso fornece apenas o caminho para uma string simples.

Se você puder garantir que os argumentos a serem passados ​​não contêm novas linhas e for aceitável alterar ligeiramente o processo de invocação, você poderá definir IFSpara conter apenas uma nova linha. Certifique-se de args.shgerar exatamente um nome de arquivo por linha, sem aspas perdidas.

IFS='
'
test.sh $(args.sh)
unset IFS

Se os argumentos puderem conter caracteres arbitrários (presumivelmente exceto bytes nulos, que você não pode passar como argumentos), você precisará realizar alguma codificação. Qualquer codificação servirá; claro, não será o mesmo que passar os argumentos diretamente: isso não é possível. Por exemplo, in args.sh(substitua \tpor um caractere de tabulação real se seu shell não suportar):

for x; do
  printf '%s_\n' "$x" |
  sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done

e em test.sh:

for arg; do
  decoded=$(printf '%s\n' "$arg" |
            sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
  decoded=${decoded%_qn}
  # process "$decoded"
done

Você pode preferir alterar test.shpara aceitar uma lista de strings como entrada. Se as strings não contiverem novas linhas, invoque args.sh | test.she use isso em args.sh(explicação):

while IFS= read -r line; do
  # process "$line"
done

Outro método que evita totalmente a necessidade de citar é invocar o segundo script do primeiro.


args.sh "$@"

informação relacionada