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.sh
e colá-la como entrada sh test.sh
funciona 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 $var
ou de comando $(cmd)
sem aspas, o resultado sofre as seguintes transformações:
- 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). - 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.sh
para 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 IFS
para conter apenas uma nova linha. Certifique-se de args.sh
gerar 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 \t
por 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.sh
para aceitar uma lista de strings como entrada. Se as strings não contiverem novas linhas, invoque args.sh | test.sh
e 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 "$@"