BASH: Неизбежное разделение слов при раскрытии подкоманды? - PullRequest
0 голосов
/ 17 июня 2020

Итак, я пишу сценарий оболочки BASH для выполнения некоторого тестирования CLI для проекта Node, над которым я работаю (я не пометил Node в этом вопросе, потому что на самом деле это относится исключительно к BASH); У меня тестирование CLI выглядит так:

test_command=$'node source/main.js --input-regex-string \'pcre/(simple)? regex/replace/vim\' -o';
echo $test_command;
$test_command 1>temp_stdout.txt 2>temp_stderr.txt;
test_code=$?;
echo "test_code $test_code"
test_stdout=`cat temp_stdout.txt`;
test_stderr=`cat temp_stderr.txt`;

Как видите, я использую кавычки в стиле C $'...', как , описанные здесь , что должно сделать это так, что $test_command буквально расширяется до node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o, который равен , что показывает echo в строке 2, однако, когда я пытаюсь запустить команду в строке 3, я получаю сообщение об ошибке, что regex/replace/vim' не является распознанным параметром командной строки в моем сценарии. Очевидно, что здесь происходит, несмотря на то, что я, казалось бы, правильно цитирую и экранирую все, BASH по-прежнему разбивает часть regex/replace/vim' на свое собственное слово . Судя по всему, что я прочитал о правилах цитирования и разделения слов c из BASH, этого не должно происходить, но все же это происходит. Я попытался изменить кавычки в первой строке, чтобы использовать сильные / буквальные ' кавычки ('node source/main.js --input-regex-string "pcre/(simple)? regex/replace/vim" -o', что просто приводит к тому, что строка 3 обрабатывает все как одно слово и, следовательно, не работает), а слабые / динамические c " кавычки ("node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o" точно так же, как пример строгой кавычки, не говоря уже о том, что, поскольку строка в кавычках в этом случае является литералом регулярного выражения, она не подходит для поведения расширения c magi " в любом случае) вместо кавычек в стиле C, изменяя экранирование самой командной строки, чтобы соответствовать любому используемому стилю кавычек; Я пробовал добавить к строке дополнительное экранирование, например test_command=$'node source/main.js --input-regex-string \\\'pcre/(simple)?\ regex/replace/vim\\\' -o, только для того, чтобы наблюдать точно такое же поведение; и я попытался изменить способ вызова команды в строке 3: цитировать расширение, заключать его в { ... } или ${ ... } с комбинациями ранее упомянутых вариантов, все из которых по-прежнему приводили к исходной проблеме разделения слов или мне просто дали общую c синтаксическую ошибку «плохая подстановка».

Короче говоря, мой вопрос в том, как правильно вызывать / форматировать сохраненную команду как строка в переменной BASH, содержащая буквальную строку в кавычках, что BASH необъяснимым образом не разделит содержащуюся в кавычках строку и не сломает всю команду?

Ответы [ 2 ]

1 голос
/ 17 июня 2020

каков правильный способ вызова / форматирования команды, хранящейся в виде строки в переменной BASH, содержащей литеральную строку в кавычках

Вы предполагаете, что нет разница между

  1. вводом команды непосредственно в терминал / скрипт
  2. сохранением точно такой же командной строки в переменной и последующим выполнением $variable.

Но есть много отличий! Команды вводятся непосредственно в bash при go большем количестве шагов обработки, чем что-либо еще. Эти шаги описаны в руководстве bash :

  1. Токенизация
    Котировки интерпретируются. Операторы определены. Команда разбивается на слова с пробелом между частями без кавычек. IFS здесь не используется.
  2. Несколько расширений слева направо. То есть, после того, как одно из этих преобразований было применено к токену, bash продолжит обрабатывать свой результат с 3. Например, вы можете безопасно использовать домашний каталог с литералом $ в его пути в результате расширения ~ не подпадает под расширение go переменной, поэтому $ остается неинтерпретированным.
    • раскрытие фигурных скобок {1..9}
    • раскрытие тильды ~
    • раскрытие параметров и переменных $var
    • арифметические значения c раскрытие $((...))
    • подстановка команд $(...), `...`
    • подстановка процесса <()
  3. Разделение слов
    Разделение результат расширений без кавычек с использованием IFS.
  4. Расширение имени файла
    Также известно как подстановка: *, ?, [...] и другие с shopt -s extglob.

По общему признанию, это смущает большинство bash новичков. Мне кажется, что большинство вопросов Stackoverflow bash касаются вещей, связанных с этими этапами обработки. Некоторые классические примеры: for i in {1..$n} не работает и echo $var не печатает то, что я назначил для var.

Строки из переменных без кавычек только при go некоторых этапах обработки, перечисленных выше. Как описано, эти шаги: «3. Разделение слов» и «4. Расширение имени файла» .

Если вы хотите применить все шаги обработки к строке, вы можете использовать команду eval. Однако это очень неодобрительно, поскольку есть либо лучшие альтернативы (если вы определяете команду самостоятельно), либо огромные последствия для безопасности (если посторонний наблюдатель определяет команду).

В вашем примере я не вижу причина вообще хранить команду. Но если вы действительно хотите получить к нему доступ как к строке где-то еще, используйте массив:

command=(node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o)
echo "${command[*]}" # print
"${command[@]}"      # execute
1 голос
/ 17 июня 2020

каков правильный способ вызова / форматирования команды, хранящейся в виде строки в переменной BASH, содержащей литеральную строку в кавычках, которая BASH не будет необъяснимым образом разделить содержащуюся в кавычках строку и сломать всю команду?

«Правильный» способ (для меня) - это не сохранить команду в виде строки в переменной. Правильный способ - использовать функцию, которая также позволяет добавлять любые logi c внутрь:

test_command() {
    node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o "$@"
}
test_command

Правильный способ - сохранить его как массив:

test_command=(node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o)
"${test_command[@]}"

существующий способ запустить сохраненную команду в виде строки в переменной - использовать eval, что является злом . Вы можете правильно избежать аргументов и объединить их в строку, а затем выполнить ее с помощью eval:

test_command=$(printf "%q " node source/main.js --input-regex-string 'pcre/(simple)? regex/replace/vim' -o)
eval "$test_command"

этого не должно происходить, но все же это происходит.

Разбиение слов выполняется на:

Оболочка просматривает результаты раскрытия параметров, подстановки команд и арифметических c раскрытий, которые не встречается в двойных кавычках для разделения слов.

Двойные или одинарные кавычки, которые привели к при расширении параметров, не являются особенными, они взяты буквально. Это важно, только если само раскрытие параметров заключено в двойные кавычки. Поскольку в вашем фрагменте кода $test_command не заключено в двойные кавычки, результатом будет слово spisted, что делает:

Оболочка обрабатывает каждый символ $ IFS как разделитель и разбивает результаты другие расширения в слова с использованием этих символов в качестве ограничителей полей.

И это не заботится о кавычках. Он заботится о них при определении аргумента при разбиении на go слов - тех, которые не заключены в двойные кавычки. Если аргумент подвергается разбиению на слова, результат просто грубо разбивается на пробелы, кавычки здесь не особенные.

...