Как передать аргументы bash-скрипта в подоболочку

13

У меня есть скрипт-обертка, который выполняет некоторую работу, а затем передает исходные параметры другому инструменту:

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

Это работает нормально, если «другой инструмент» не запускается в подоболочке:

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

Если я назову мой скрипт-обертку так:

wrapper.sh -x "blah blup"

затем только первый оригинальный аргумент (-x) передается «other_tool». На самом деле я не создаю подоболочку, а передаю исходные аргументы оболочке на телефоне Android, что не должно иметь никакого значения:

#!/bin/bash
# ...
adb sh -c "other_tool -a -b $@"
Ральф Холли
источник

Ответы:

14

printfКоманда Bash имеет функцию, которая будет заключать в кавычки / экранировать / независимо от строки, так что если родительский и подоболочка на самом деле bash, это должно работать:

[Редактировать: как указал siegi в комментарии, если вы делаете это очевидным образом, возникает проблема, когда аргументы не передаются, когда он действует так, как будто на самом деле был один пустой аргумент. Я добавил обходной путь ниже, заключив строку формата в ${1+}, которая включает строку формата, только если первый аргумент определен . Это немного круто, но это работает.]

#!/bin/bash

quoted_args="$(printf "${1+ %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"

Обратите внимание, что вы также можете сделать это в одной строке: bash -c "other_tool -a -b$(printf "${1+ %q}" "$@")"

Гордон Дэвиссон
источник
Спасибо! Это сработало очень хорошо для меня.
DribblzAroundU82
Это не работает правильно, когда не передается ни один аргумент (вместо одного аргумента он передает один пустой аргумент).
siegi
1
@siegi Хороший улов; Я добавил обходной путь для этого.
Гордон Дэвиссон
4

Ни одно из решений не работает хорошо. Просто передайте x / \ \ "b \" / aaaaa / \ 'xxx \ yyyy \' / zz \ ​​"offf \" в качестве параметра, и они не пройдут.

Вот простая оболочка, которая обрабатывает каждый случай. Обратите внимание, как он избегает каждого аргумента дважды.

#!/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[*]}
jcayzac
источник
1
также полезно, если вы пытаетесь объединить $ @ как часть возможного строкового аргумента в кавычках, такого как '/ bin / foo' "$ @" '--opt', и обнаруживаете, что он не получается так, как вы ожидаете ,
JRG
1
О, слава богу. Бороться с этим вопросом и, похоже, ничего не получалось. Это решило это. Было бы неплохо, если бы это был каким-то образом встроенный bash для вызова двойного экранирования, учитывая, что эта проблема часто возникает.
MooGoo
2

Изменить $@на $*. Я сделал небольшой локальный тест, и он работает в моем случае.

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

Сохранение как test.shи выполнение его дает

$ ./test.sh foo bar
foo bar
foo

Между ними $*и $@, как вы можете видеть, есть небольшая разница . Смотрите, например, http://ss64.com/bash/syntax-parameters.html


Для последующего вопроса в комментариях: вам нужно экранировать, например, пробел «дважды», чтобы передать строку с разделителем в качестве объединенного аргумента, например, с test.shизменением в wcоболочку:

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

Это работает:

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

но:

$ ./test.sh -l "test file"
wc: test: No such file or directory
wc: file: No such file or directory
0 total
Даниэль Андерссон
источник
К сожалению, это тоже не работает. Если вы называете обертку следующим образом: wrapper.sh -x "blah blup"тогда подоболочка получает ТРИ параметра (-x, бла, блеф) вместо ДВУХ (-x, «бла блюп»)
Ральф Холли
Вы должны «двойной побег» Аргумент , если вы хотите, чтобы пространство сепаратор должны быть сохранены, то есть "Blah\ blup". Попробуйте и посмотрите, работает ли это.
Даниэль Андерссон
2
Похоже, что это работает, однако для этого требуется вызывающая сторона wrapper.sh, чтобы добавить экранирование, и я ищу прозрачное решение.
Ральф Холли
2

Это терпит неудачу, потому что вы приводите массив (позиционные параметры) в строку. "$@"магический, потому что он дает вам каждый отдельный параметр в виде строки в правильных кавычках. Добавление дополнительного текста разрушает магию: "blah $@"это всего лишь одна строка.

Это может приблизить вас:

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

Конечно, любой параметр, содержащий одну кавычку, вызовет проблемы.

Гленн Джекман
источник
2

Хорошо, больше объяснений:

$ 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
Джерри
источник
1

Я пробовал много разных решений, хороший ресурс, включая справочную информацию и альтернативы, например, BashFAQ / 096 на вики Грега (он же GreyCat) . В целом я обнаружил, что следующие два являются наиболее читаемыми (из рабочих):


Начиная с Bash 4.4 (насколько я мог судить из новостей ), можно использовать расширение параметра @Q следующим образом:

adb sh -c "other_tool -a -b ${*@Q}"

Обратите внимание, что я использую $*здесь вместо того, $@потому что вы хотите "other_tool -a -b ${*@Q}"быть одной единственной строкой вместо одной строки на переданный аргумент.

Если вы хотите сделать то же самое с переменной массива bash, вам потребуется синтаксис ${ARRAY[*]@Q}(в кавычках).


Если у вас нет Bash 4.4 или более поздней версии или вы не уверены, это мое предпочтительное решение:

function escapeBashArgs() {
    local arg separator=""
    for arg
    do
        printf "%s%q" "$separator" "$arg"
        separator=" "
    done
}

adb sh -c "other_tool -a -b $(escapeBashArgs "$@")"

Обратите внимание, что здесь вам нужно использовать "$@"вместо $@или "$*"или $*потому, что вы не хотите разделять слова в аргументах, поэтому варианты без кавычек не могут быть использованы, и вы хотите, чтобы количество аргументов было сохранено, поэтому "$*"не может быть использовано, так как оно объединяет все аргументы в одну строку. Затем функция возвращает все аргументы в одной строке.

Если вас не волнует дополнительное пространство перед первым аргументом, вы можете изменить printfстроку формата на " %q"и удалить separatorпеременную. Или вы можете использовать однострочник от Гордона Дэвиссона .


Это решение работает во всех случаях, которые я мог придумать, особенно:

  • Нет аргументов: escapeBashArgsничего
  • Пустые аргументы: escapeBashArgs "" ""'' ''
  • Аргументы только с пробелами: escapeBashArgs " " " "' ' ' 'или \ \ \ \ \( последний пробел используется этим рендерером сайтов )
  • Аргументы со специальным интервалом и символами новой строки: escapeBashArgs "a b" c\ d "arg with newline"'a b' 'c d' $'arg with\nnewline'или a\ \ \ \ \ \ b c\ d $'arg with\nnewline'( символ новой строки находится между withи newline, в других позициях это из-за переноса строк на этом сайте )
  • Аргументы со специальными символами: escapeBashArgs '$"'\''({:})''$"'\''({:})'или\$\"\'\(\{:\}\)
  • Пример ответа от jcayzacs : escapeBashArgs x/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"'x/ "b"/aaaaa/'\''xxx yyyy'\''/zz"offf"'илиx/\ \ \"b\"/aaaaa/\'xxx\ yyyy\'/zz\"offf\"

(Протестировано с GNU bash 5.0.3 (1) -релизом.)

siegi
источник
-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"
Джерри
источник
3
Добро пожаловать в Супер пользователя! Мы ищем длинные ответы, которые дают некоторое объяснение и контекст. Не просто дать ответ в одну строку; объясните, почему ваш ответ правильный, в идеале с цитатами. Ответы, не содержащие объяснений, могут быть удалены.
G-Man говорит: «Восстанови Монику»