Як передавати аргументи bash script до підшару


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 має функцію, яка буде цитувати / Escape / будь-яку рядок, так що, поки і батьківська, і нижня черга насправді є 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[*]}

1
також корисно, якщо ви намагаєтеся об'єднати $ @ як частину можливого аргументованого рядкового аргументу, наприклад "/ bin / foo" "$ @" '--opt "і виявивши, що він просто не виходить так, як ви очікували .
jrg

1
О слава богу. Борявся з цим питанням і, здавалося б, нічого не вийшло. Це вирішило це. Було б добре, якби це якимось чином вбудований баш, щоб викликати подвійні втечі, враховуючи, що ця проблема виникає багато.
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, blah, blup) замість TWO (-x, "blah blup")
Ральф Холлі

Вам потрібно "подвоїти" аргумент, якщо ви хочете, щоб роздільник простору був збережений, тобто "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's) . Всього я виявив наступні два найбільш читабельні (з робочих):


Оскільки 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) -release.)


-2
#! /bin/bash
sh -c 'other_tool -a -b "$@"' -- "$@"

3
Ласкаво просимо до Супер Користувача! Ми шукаємо довгі відповіді, які дають певні пояснення та контекст. Не просто дайте однорядну відповідь; поясніть, чому ваша відповідь правильна, в ідеалі - цитатами. Відповіді, які не містять пояснень, можуть бути видалені.
G-Man каже "Відновити Моніку"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.