Допустим, у меня есть следующие два скрипта оболочки:
#!/bin/sh
#This script is named: args.sh
echo 1 "\"Two words\"" 3
, и:
#!/bin/sh
#This script is named: test.sh
echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3
Когда я вызываю скрипты следующим образом:
sh test.sh $(sh args.sh)
, Я получаю:
Argument 1: 1
Argument 2: "Two
Argument 3: words"
Когда я ожидал вместо этого получить:
Argument 1: 1
Argument 2: Two words
Argument 3: 3
Копирование вывода sh args.sh
и вставка его в качестве ввода sh test.sh
работает отлично; поэтому я предполагаю, что это не то, что на самом деле делает оболочка. Я могу получить желаемый/ожидаемый вывод, вызвав sh args.sh | xargs sh test.sh
вместо этого.
Однако мне интересно, есть ли эквивалентный способ сделать это без перенаправления вывода первого скрипта (args.sh) в xargs? Я хочу, чтобы скрипты вызывались в исходном порядке; аргументный скрипт выводил параметры второму. Я также ищу объяснение, почему этот вызов не работает так, как ожидалось?
решение1
Часть проблемы заключается в том, что строка, возвращаемая args.sh, не анализируется так же, как прямая команда, а только по значению $IFS ( $' \t\n'
). Попробуйте включить трассировку команд с помощью 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"
$
Обратите внимание на строку, начинающуюся с одного +
: есть четыре аргумента, '"Two'
и 'words"'
два из них анализируются как отдельные аргументы. Что вам нужно сделать, так это изменить $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
$
Это не будет работать для каждого вывода. Лучше всего изменить вывод args.sh, чтобы отделить вывод чем-то отличным от пробела, например, запятой или двоеточием:
$ 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
$
решение2
$var
Если подстановку переменной или команды оставить $(cmd)
без кавычек, результат претерпит следующие преобразования:
- Разделить полученную строку на слова. Разделение происходит по пробелам (последовательности пробелов, табуляции и новой строки); это можно настроить с помощью параметра
IFS
(см.1,2). - Каждое полученное слово обрабатывается как шаблон поиска, и если оно соответствует некоторым файлам, слово заменяется списком имен файлов.
Обратите внимание, что результатом является не строка, а список строк. Кроме того, обратите внимание, что символы кавычек, такие как , "
здесь не задействованы; они являются частью синтаксиса исходного кода оболочки, а не расширения строки.
Общее правило программирования оболочки — всегда заключать подстановки переменных и команд в двойные кавычки, если только вы не знаете, почему их нужно оставлять. Так что в test.sh
, напишите echo "Argument 1: $1"
. Чтобы передать аргументы в test.sh
, у вас возникнут проблемы: вам нужно передать список слов из args.sh
в test.sh
, но выбранный вами метод подразумевает подстановку команды, а это дает возможность только простой строки.
Если вы можете гарантировать, что передаваемые аргументы не содержат никаких новых строк, и допустимо немного изменить процесс вызова, вы можете задать, чтобы он IFS
содержал только новую строку. Убедитесь, что args.sh
выводится ровно одно имя файла на строку без лишних кавычек.
IFS='
'
test.sh $(args.sh)
unset IFS
Если аргументы могут содержать произвольные символы (предположительно, за исключением нулевых байтов, которые вы не можете передать как аргументы), вам нужно будет выполнить некоторую кодировку. Подойдет любая кодировка; конечно, это не будет то же самое, что передача аргументов напрямую: это невозможно. Например, в args.sh
(замените \t
на реальный символ табуляции, если ваша оболочка его не поддерживает):
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
И в 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
Вы можете предпочесть изменить, test.sh
чтобы принять список строк в качестве входных данных. Если строки не содержат новых строк, вызовите args.sh | test.sh
и используйте это в args.sh
(объяснение):
while IFS= read -r line; do
# process "$line"
done
Другой метод, который полностью устраняет необходимость цитирования, — это вызов второго скрипта из первого.
…
args.sh "$@"