Como passar argumentos de script bash para um subshell

9

Eu tenho um script de wrapper que faz algum trabalho e depois passa os parâmetros originais para outra ferramenta:

#!/bin/bash
# ...
other_tool -a -b "$@"

Isso funciona bem, a menos que a "outra ferramenta" seja executada em um subshell:

#!/bin/bash
# ...
bash -c "other_tool -a -b $@"

Se eu chamar meu script de wrapper assim:

wrapper.sh -x "blah blup"

então, somente o primeiro argumento original (-x) é passado para "other_tool". Na realidade, não criei um subshell, mas passe os argumentos originais para um shell em um telefone Android, o que não deve fazer nenhuma diferença:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"
    
por Ralf Holly 21.03.2012 / 15:56

6 respostas

12

O comando printf do Bash tem um recurso que vai citar / escapar / qualquer que seja uma string, portanto, contanto que tanto o pai quanto o subshell sejam realmente bash, isso deve funcionar:

#!/bin/bash

quoted_args="$(printf " %q" "$@")" # Note: this will have a leading space before the first arg
# echo "Quoted args:$quoted_args" # Uncomment this to see what it's doing
bash -c "other_tool -a -b$quoted_args"

Note que você também pode fazer isso em uma única linha: bash -c "other_tool -a -b$(printf " %q" "$@")"

    
por 21.03.2012 / 21:02
4

Nenhuma das soluções funciona bem. Apenas passe por x / \ \ \ "b \" / aaaaa / \ 'xxx \ aaaa \' / zz \ "offf \" como parâmetro e eles falham.

Aqui está um wrapper simples que lida com todos os casos. Observe como ele escapa de cada argumento duas vezes.

#!/usr/bin/env bash
declare -a ARGS
COUNT=$#
for ((INDEX=0; INDEX<COUNT; ++INDEX))
do
    ARG="$(printf "%q" "$1")"
    ARGS[INDEX]="$(printf "%q" "$ARG")"
    shift
done

ls -l ${ARGS[*]}
    
por 18.11.2012 / 05:04
3

Altere $@ para $* . Eu fiz um pequeno teste local e funciona no meu caso.

#!/bin/sh
bash -c "echo $*"
bash -c "echo $@"

Salvar como test.sh e torná-lo executável resulta em

$ ./test.sh foo bar
foo bar
foo

Há uma diferença sutil entre $* e $@ , como você pode ver. Veja por exemplo link

Para a pergunta de acompanhamento nos comentários: você precisa fugir, e. espaço em branco "duas vezes" para passar uma cadeia com um separador como um argumento combinado, e. com test.sh modificado para um wrapper wc :

#!/bin/sh
bash -c "wc $*"

Isso funciona:

$ touch test\ file
$ ./test.sh -l "test\ file"
0 test file

mas:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
    
por 21.03.2012 / 16:02
2

Está falhando porque você está coagindo uma matriz (os parâmetros posicionais) em uma string. "$@" é mágico porque fornece cada parâmetro separado como uma string citada corretamente. Adicionar texto adicional quebra a mágica: "blah $@" é apenas uma única string.

Isso pode aproximar você:

cmd="other_tool -a -b"
for parm in "$@"; do cmd+=" '$parm'"; done
adb sh -c "$cmd"

Naturalmente, qualquer parâmetro que contenha uma aspa simples causará problemas.

    
por 21.03.2012 / 16:47
2

Ok, mais explicações:

$ cat /tmp/test.sh
#! /bin/bash

echo '$@='"$@"

set -v # "debug"

sh -c 'echo $@' "$@" # gremlins steal the first option

sh -c 'echo $@' -- "$@" # We defend the option with two knifes

$ bash -e /tmp/test.sh first second third
$@=first second third

sh -c 'echo $@' "$@" # gremlins steal the first option
second third

sh -c 'echo $@' -- "$@" # We defend the option with two knifes
first second third
    
por 11.05.2015 / 01:50
-1
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
    
por 09.05.2015 / 07:42

Tags