Как правильно передать имена файлов другим программам в скриптах bash? - PullRequest
3 голосов
/ 11 ноября 2010

Какую идиому использовать в скриптах Bash (пожалуйста, без Perl, Python и т. П.) для создания командной строки для другой программы из аргументов скрипта при правильной обработке имен файлов правильно * * 1005

Под правильно я имею в виду обработку имен файлов с пробелами или нечетными символами без непреднамеренного побуждения другой программы обрабатывать их как отдельные аргументы (или, в случае < или > & mdash; что в конце концов, действительны, если неудачные символы имени файла, если их правильно экранировать - что-то еще хуже).

Вот выдуманный пример того, что я имею в виду, в форме, которую не правильно обрабатывает имена файлов: Давайте предположим, что этот скрипт (foo) создает командную строку для команды (bar (предполагается, что он находится в пути), взяв все входные аргументы foo и переместив все, что выглядит как флаг, вперед, а затем вызвав bar:

#!/bin/bash
# This is clearly wrong

FILES=
FLAGS=
for ARG in "$@"; do
    echo "foo: Handling $ARG"
    if [ x${ARG:0:1} = "x-" ]; then
        # Looks like a flag, add it to the flags string
        FLAGS="$FLAGS $ARG"
    else
        # Looks like a file, add it to the files string
        FILES="$FILES $ARG"
    fi
done

# Call bar with the flags and files (we don't care that they'll
# have an extra space or two)
CMD="bar $FLAGS $FILES"
echo "Issuing: $CMD"
$CMD

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

В наивном сценарии с простыми именами файлов это прекрасно работает. Но если мы предположим каталог, содержащий файлы

one
two
three and a half
four < five

тогда, конечно, команда foo * терпит неудачу в своей задаче:

foo: Handling four < five
foo: Handling one
foo: Handling three and a half
foo: Handling two
Issuing: bar   four < five one three and a half two

Если мы действительно позволим foo выполнить эту команду, то результат будет не таким, как мы ожидаем.

Раньше я пытался справиться с этим с помощью простого способа обеспечить наличие кавычек вокруг каждого имени файла, но я (очень) быстро понял, что это не правильный подход. : -)

Так что же? Ограничения:

  1. Я хочу, чтобы идиома была максимально простой (не в последнюю очередь, чтобы я мог ее запомнить).
  2. Я ищу идиому общего назначения, поэтому я составляю программу bar и надуманный пример выше, а не использую реальный сценарий, когда люди могут легко (и разумно) пойти по пути попыток использовать функции в целевой программе.
  3. Я хочу придерживаться скрипта Bash, я не хочу вызывать Perl, Python и т. Д.
  4. Я в порядке, полагаясь на (другие) стандартные * nix-утилиты, такие как xargs, sed или tr, при условии, что мы не слишком тупые (см. # 1 выше). (Извинения программистам на Perl, Python и т. Д., Которые думают, что # 3 и # 4 объединяются, чтобы провести произвольное различие.)
  5. Если это имеет значение, целевой программой также может быть Bash-скрипт или нет. Я не ожидал, что это будет иметь значение ...
  6. Я не просто хочу обрабатывать пробелы, я хочу правильно обрабатывать и странные символы.
  7. Меня не беспокоит, если он не обрабатывает имена файлов со встроенными нулевыми символами (буквально код символа 0). Если кому-то удалось создать его в своей файловой системе, я не беспокоюсь об этом, он попытался очень тяжело все испортить.

Заранее спасибо, ребята.


Редактировать : Игнасио Васкес-Абрамс указал мне на Bash FAQ entry # 50 , что после некоторого чтения и экспериментов, кажется, указывает на то, что одним из способов является использовать Bash массивы :

#!/bin/bash
# This appears to work, using Bash arrays

# Start with blank arrays
FILES=()
FLAGS=()
for ARG in "$@"; do
    echo "foo: Handling $ARG"
    if [ x${ARG:0:1} = "x-" ]; then
        # Looks like a flag, add it to the flags array
        FLAGS+=("$ARG")
    else
        # Looks like a file, add it to the files array
        FILES+=("$ARG")
    fi
done

# Call bar with the flags and files
echo "Issuing (but properly delimited, not exactly as this appears): bar ${FLAGS[@]} ${FILES[@]}"
bar "${FLAGS[@]}" "${FILES[@]}"

Это правильно и разумно? Или я полагаюсь на что-то более важное, что укусит меня позже. Кажется, что работает, и это помечает все остальные поля для меня (просто, легко запомнить и т. Д.) Похоже, что он полагается на недавнюю функцию Bash относительно (запись FAQ № 50 упоминает v3.1, но я не был уверен, были ли это вообще массивы некоторых из синтаксиса, который они использовали с ним) , но я думаю, что, скорее всего, я буду иметь дело только с версиями, которые имеют его.

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

Ответы [ 3 ]

5 голосов
/ 11 ноября 2010

Почему вы хотите «собрать» команду?Добавьте файлы и флаги в массивы, используя правильные кавычки, и введите команду напрямую, используя в качестве аргументов цитируемые массивы.

Выбранные строки в вашем сценарии (без изменений):

if [[ ${ARG:0:1} == - ]]; then    # using a Bash idiom
FLAGS+=("$ARG")                   # add an element to an array
FILES+=("$ARG")
echo "Issuing: bar \"${FLAGS[@]}\" \"${FILES[@]}\""
bar "${FLAGS[@]}" "${FILES[@]}"

Длякраткое демонстрационное использование массивов таким образом:

$ a=(aaa 'bbb ccc' ddd); for arg in "${a[@]}"; do echo "..${arg}.."; done

Вывод:

..aaa..
..bbb ccc..
..ddd..

Пожалуйста, смотрите BashFAQ / 050 относительно размещения команд в переменных.Причина, по которой ваш скрипт не работает, заключается в том, что нет способа заключать аргументы в строку в кавычках.Если вы поместите туда кавычки, они будут считаться частью самой строки, а не разделителями.Если аргументы оставлены без кавычек, выполняется разбиение слов, а аргументы, содержащие пробелы, рассматриваются как несколько аргументов.Аргументы с "<", ">" или "|"в любом случае это не проблема, поскольку перенаправление и конвейерная обработка выполняются до раскрытия переменной, поэтому они рассматриваются как символы в строке.

Путем помещения аргументов (имен файлов) в массив, пробелы, переводы строки и т. Д. Сохраняются.Заключая в кавычки переменную массива, когда она передается в качестве аргумента, они сохраняются на пути к программе-потребителю.

Некоторые дополнительные примечания:

  • Используйте строчную (или смешанную) переменнуюимена, чтобы уменьшить вероятность их столкновения со встроенными переменными оболочки.
  • Если вы используете одинарные квадратные скобки для условных выражений в любой современной оболочке, архаичная идиома "x" больше не нужна, если вы заключаете в кавычки переменные (см. мой ответ здесь ).Однако в Bash используйте двойные скобки.Они предоставляют дополнительные функции (см. Мой ответ здесь ).
  • Используйте getopts, как предложил Let_Me_Be.Ваш скрипт, хотя я знаю, что это только пример, не сможет обрабатывать переключатели, которые принимают аргументы.
  • Этот for ARG in "$@" можно сократить до этого for ARG (но я предпочитаю читаемость более явноговерсия).
1 голос
/ 11 ноября 2010

См. BashFAQ # 50 (а также, возможно, # 35 при разборе параметров).Для описываемого вами сценария, где вы динамически строите команду, лучше всего использовать массивы, а не простые строки, поскольку они не потеряют отслеживание границ слова.Общие правила: для создания массива вместо VAR="foo bar baz" используйте VAR=("foo" "bar" "baz");чтобы использовать массив вместо $VAR, используйте "${VAR[@]}".Вот рабочая версия вашего примера сценария, использующего этот метод:

#!/bin/bash
# This is clearly wrong

FILES=()
FLAGS=()
for ARG in "$@"; do
    echo "foo: Handling $ARG"
    if [ x${ARG:0:1} = "x-" ]; then
        # Looks like a flag, add it to the flags array
        FLAGS=("${FLAGS[@]}" "$ARG") # FLAGS+=("$ARG") would also work in bash 3.1+, as Dennis pointed out
    else
        # Looks like a file, add it to the files string
        FILES=("${FILES[@]}" "$ARG")
    fi
done

# Call bar with the flags and files (we don't care that they'll
# have an extra space or two)
CMD=("bar" "${FLAGS[@]}" "${FILES[@]}")
echo "Issuing: ${CMD[*]}"
"${CMD[@]}"

Обратите внимание, что в команде echo я использовал "${VAR[*]}" вместо формы [@], потому что нет необходимости / указывать на сохранение словаломается здесь.Если вы хотите напечатать / записать команду в однозначной форме, это будет намного сложнее.

Кроме того, это не дает вам возможности создавать перенаправления или другие специальные параметры оболочки в встроенной команде - если выдобавьте >outfile в массив FILES, он будет рассматриваться как просто еще один аргумент команды, а не перенаправление оболочки.Если вам нужно программно построить их, будьте готовы к головным болям.

0 голосов
/ 11 ноября 2010

getopts должен уметь правильно обрабатывать пробелы в аргументах ("file name.txt").Странные персонажи также должны работать, если они правильно экранированы (ls -b).

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