Вывод сценария оболочки неправильно разбивается при передаче в качестве аргумента скрипту

Предположим, у меня есть следующие два сценария оболочки:

#!/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 sh args.sh | xargs sh test.sh вместо этого.

Тем не менее, мне интересно, есть ли эквивалентный способ сделать это, не передавая вывод первого скрипта (args.sh) в xargs? Я хочу, чтобы сценарии вызывались в исходном порядке; сценарий аргумента для вывода параметров во второй. Я также ищу объяснение, почему этот вызов работает не так, как ожидалось?

2 Solutions collect form web for “Вывод сценария оболочки неправильно разбивается при передаче в качестве аргумента скрипту”

Часть проблемы состоит в том, что строка, возвращаемая 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 $ 

Когда вы оставляете переменную подстановку $var или подстановку команд $(cmd) кавычек, результат претерпевает следующие преобразования:

  1. Разделите полученную строку на слова. Разделение происходит в пробелах (последовательности пробелов, вкладок и строк новой строки); это можно настроить, установив IFS (см. 1 , 2 ).
  2. Каждое из полученных слов рассматривается как шаблон glob, и если он соответствует некоторым файлам, слово заменяется на список имен файлов.

Обратите внимание, что результатом является не строка, а список строк. Кроме того, обратите внимание, что символы цитирования типа " здесь не задействованы, они являются частью синтаксиса источника оболочки, а не расширения строки.

Общее правило программирования оболочки – всегда включать двойные кавычки вокруг переменных и подстановок команд, если вы не знаете, почему их нужно оставить. Итак, в 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 | test.sh и использовать это в args.sh ( объяснение ):

 while IFS= read -r line; do # process "$line" done 

Другим методом, который предотвращает необходимость цитирования в целом, является вызов второго скрипта с первого.

 … args.sh "$@" 
  • Как написать вывод на экран из службы systemd во время загрузки?
  • Есть ли способ запустить зашифрованный (GPG) файл на лету в сценарии?
  • Чтение значений массива в качестве пользовательского ввода дает неправильную длину массива и только -a или -p работает при чтении
  • Простой оператор if / else не работает
  • Как запустить несколько программ в фоновом режиме с помощью одной команды?
  • Совпадение шаблонов
  • Как прочитать вывод команд в скрипте
  • Самый изящный способ прекратить навязчивую программу
  • bash, не используя ssh -i. Не удается найти каталог
  • inotifywait - получить старое и новое имя файла при переименовании
  • Как удалить первые n строк файла ascii с помощью команд оболочки?
  • Interesting Posts
    Linux и Unix - лучшая ОС в мире.