Кавычки в аргументах вызова Node.js

Я использую двойные кавычки в Node.js spawn аргументы, потому что они могут содержать пробелы:

const excludes = ['/foo/bar', '/foo/baz', '/foo/bar baz'];
const tar = spawn('tar', [
  '--create', '--gzip',
  // '--exclude="/foo/bar"', '--exclude="/foo/baz"', '--exclude="/foo/bar baz"'
  ...excludes.map(exclude => `--exclude="${exclude}"`),
  '/foo'
], { stdio: ['ignore', 'pipe', 'inherit'] });

По какой-то причине tar игнорируемых --exclude аргументы, которые представлены таким образом. Результат такой же с spawn являющийся require('child_process').spawn а также require('cross-spawn'),

--exclude работает как положено, когда нет двойных кавычек для путей, которые не требуют их.

И то же самое работает, как и ожидалось от оболочки, даже с двойными кавычками:

tar --create --gzip --exclude="/foo/bar" --exclude="/foo/baz" /foo > ./foo.tgz

Я не уверен, что там происходит и как spawn может быть отлажен, чтобы проверить, выполняет ли он какое-то странное экранирование для двойных кавычек.

4 ответа

Это проблема в приоритете типа цитаты. Двойные кавычки имеют приоритет над одинарными, поэтому вызов spawn не работает.

Системная оболочка удаляет кавычки вокруг параметров, поэтому программа получает значение без кавычек в конце. Создание процесса обходит этот шаг, поскольку он обходит оболочку, поэтому программа получает эти буквенные кавычки как часть параметра и не знает, как правильно их обрабатывать.

Есть два реальных варианта решения этой проблемы, о которых я знаю:

  1. Это нелогично, но переключение типов кавычек должно решить эту проблему. Переключите приведенный выше код на:

    const tar = spawn("tar", [
      "--create", "--gzip",
      "--exclude='/foo/bar'", "--exclude='/foo/baz'", "/foo"
    ], { stdio: ["ignore", "pipe", "inherit"] });
    
  2. Кроме того, вы можете использовать { shell: true } и использовать ваше текущее форматирование. Это пропустит запрос spawn через оболочку, поэтому будет выполнен шаг синтаксического анализа, который в данный момент пропускается. Подробнее об этом здесь.

    const tar = spawn('tar', [
      '--create', '--gzip',
      '--exclude="/foo/bar"', '--exclude="/foo/baz"', '/foo'
    ], { stdio: ['ignore', 'pipe', 'inherit'], shell: true });
    

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

В этом случае вы можете:

spawn(exe, args, { windowsVerbatimArguments: true });

См. Документы:

windowsVerbatimArguments <boolean>В Windows не выполняется цитирование или экранирование аргументов. Игнорируется в Unix. Это установлено наtrue автоматически, когда shellуказан и является CMD. По умолчанию: false.

Вы должны понимать, как оболочка обрабатывает пробелы и кавычки. Я говорю "оболочка" - есть разные оболочки, и я не знаю различий между ними, поэтому возможно, что то, что я собираюсь написать, к вам не относится. Кто-то может свободно редактировать это, так что это более точно.

Существуют всевозможные синтаксические сложности, которые можно включить в команду оболочки: переданные по конвейеру команды, входные и выходные файлы, интерполированные переменные, интерполированные команды, переменные окружения и как минимум 4 (да, четыре) различных способа цитирования строк. Но для целей этого вопроса давайте просто скажем, что команда оболочки - это имя команды, за которым следует (возможно, пустой) список строковых аргументов. Имя команды может быть встроенной командой (cd, ls, sudoи т. д.) или это может быть исполняемый файл. Или, другими словами, команда оболочки представляет собой список из одной или нескольких строк (включая первую строку, которая сообщает оболочке, что это за команда).

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

tar --create --exclude=/foo/bar /foo
tar --create --exclude='/foo/bar' /foo
tar --create --exclude="/foo/bar" /foo
tar --create '--exclude=/foo/bar' /foo
tar --create "--exclude=/foo/bar" /foo

В каждом случае команда запускает исполняемый файл tar со списком аргументов --create, --exclude=/foo/bar, /foo,

Обратите внимание на поведение кавычек, которое отличается от всех других языков, которые я знаю. В большинстве языков строковый литерал полностью заключен в кавычки - так компилятор / интерпретатор знает, где они начинаются и заканчиваются. Но в командах оболочки пробел - это то, что сообщает оболочке, где заканчивается один аргумент и начинается следующий. (Пробельные / экранированные пробелы не учитываются.) Единственная цель кавычек - изменить способ обработки некоторых символов. Команды оболочки очень гибки в этом отношении, поэтому следующие команды также эквивалентны приведенным выше:

tar -"-"create --exc'lude=/fo'o/bar /foo
tar --cr'eate' --exclude"="/foo"/bar" /foo

И когда я говорю, что эти команды эквивалентны, я имею в виду tar исполняемый файл не может знать, какой из них был вызван. То есть невозможно записать исполняемый файл mycommand такой, что команды mycommand foo а также mycommand "foo" записать другой вывод в STDOUT или STDERR, или вернуть разные коды выхода, или иначе вести себя по-разному.

Однако при запуске команд оболочки из nodejs вам не нужно использовать функции оболочки для конвейерной передачи, потоковой передачи в / из файлов, интерполяции переменных и т. Д., Потому что javascript может обрабатывать все эти вещи, если вы хотите. Поэтому, когда вы предоставляете аргументы spawnэто обходит эти особенности оболочки; он ничего не делает со специальными символами оболочки. Вы просто предоставляете аргументы напрямую. Так что в следующем примере один из аргументов будет --exclude=/foo/bar baz, что приведет к tar игнорировать файл / каталог с именем bar baz в /foo каталог:

const tar = spawn('tar', [
  '--create', '--gzip',
  '--exclude=/foo/bar', '--exclude=/foo/baz', '--exclude=/foo/bar baz',
  '/foo'
], { stdio: ['ignore', 'pipe', 'inherit'] });

(Хотя очевидно, что если вы используете строковые литералы javascript, вам может потребоваться экранировать некоторые символы на уровне javascript.)

Мне не нравится ни один из ответов Джошуна. (1) даже не работал для меня, и я удивлен, что это сработало для него - если это сработало, то я рассматриваю это как ошибку в nodejs (или, возможно, в tar). (Я запускаю nodejs v6.9.5 в Ubuntu 16.04.3 LTS, с GNU tar v1.28.) Что касается (2), это означает излишнее введение всех сложностей обработки строк оболочки в ваш код javascript. Как сказано в документации:

Примечание: если shell опция включена, не передавайте необработанный ввод пользователя этой функции. Любой ввод, содержащий метасимволы оболочки, может использоваться для запуска выполнения произвольной команды.

Я, например, не знаю всех тонкостей экранирования оболочки, поэтому я бы не рискнул бежать spawn с shell вариант с ненадежным вводом.

The shellвариант опасен, потому что вы можете ввести непереносимые команды. НапримерNODE_ENV=production webpackбудет работать только в Linux, но не работает в Windows (настройка env)

Было бы лучше полностью разделить составные аргументы и позволить системе делать магию. Например:

      const tar = spawn('tar', [
  '--create', '--gzip',
  '--exclude', "/foo/don't bar"
]);
Другие вопросы по тегам