Я читал о том, что я должен заключать в кавычки переменные в bash, например, «$ foo» вместо $ foo. Однако при написании скрипта я обнаружил случай, когда он работает без кавычек, но не с ними:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Этот работает:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Этот не делает (обратите внимание на двойные кавычки вокруг $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Что является причиной этого?
Является ли первая строка хорошей версией; или я должен подозревать, что где-то есть скрытая ошибка, которая вызывает такое поведение?
В общем, где я могу найти хорошую документацию, чтобы понять, как работает bash и его цитирование? Во время написания этого сценария я чувствую, что начал работать методом проб и ошибок вместо понимания правил.
wget
не знаете, что--mirror --no-host-directories
значит (как один аргумент), но он обрабатывает его, когда он разбит на два аргумента. Очень немногие программы обрабатывают пробелы и кавычки, особенно когда они находятся внутри вектора аргумента. Проблема в томbash
, что и другие оболочки должны быть>bash
, так что вы можете себе представить, что$a
это точно эквивалентно непосредственному написанию его содержимого. Теперь проблема очевидна:a="-a -b"; cmd "$a"
расширяетсяcmd "-a -b"
, но,cmd
вероятно, не знает, что это значит.cmd $a
расширяется доcmd -a -b
, что, вероятно , работает.Ответы:
По сути, вы должны заключать переменные в двойные кавычки, чтобы защитить их от разбиения слов (и генерации имени файла). Однако в вашем примере
разделение слов это именно то, что вы хотите .
С
"$wget_options"
(цитируется),wget
не знает, что делать с единственным аргументом--mirror --no-host-directories
и жалуетсяДля того,
wget
чтобы увидеть два варианта--mirror
и--no-host-directories
как отдельно, должно произойти разделение слов.Есть более надежные способы сделать это. Если вы используете
bash
или какую-либо другую оболочку, которая использует массивы, такие какbash
do, см. ответ Гленна Джекмана . Ответ Жиля дополнительно описывает альтернативное решение для простых оболочек, такое как стандарт/bin/sh
. Оба по существу хранят каждый параметр как отдельный элемент в массиве.Связанный вопрос с хорошими ответами: почему мой сценарий оболочки задыхается от пробелов или других специальных символов?
Расширение переменных в двойных кавычках - это хорошее правило. Сделай это . Тогда знайте о очень немногих случаях, когда вы не должны этого делать. Они будут представлены вам через диагностические сообщения, такие как приведенное выше сообщение об ошибке.
Есть также несколько случаев, когда вы не нужно заключать в кавычки расширения переменных. Но в любом случае проще продолжать использовать двойные кавычки, поскольку это не имеет большого значения. Одним из таких случаев является
Еще один
источник
$IFS
содержит правильное значение. Здесь вам нужно разделить пробел, и текст не будет содержать табуляции или новой строки, поэтому будет использоваться значение по умолчанию$IFS
, но если этот код будет использоваться в функции, которая может быть вызвана в контексте, где ее$IFS
можно было изменить , вы хотели бы установить$IFS
заранее (и, возможно, восстановить его позже или использовать локальную область видимости для него, если остальная часть кода предполагает неизмененный$IFS
)Самый надежный способ написания кода - использовать массив:
источник
cp
иrsync
будут делать неожиданные вещи, если ваша команда расширяется до чего-то вродеrsync '' rest of parameters
. Это отлично подходит для условного построения команды по частям, а затем просто запускает ее один раз в одном месте.Вы пытаетесь сохранить список строк в строковой переменной. Это не подходит. Независимо от того, как вы получаете доступ к переменной, что-то сломано.
wget_options='--mirror --no-host-directories'
устанавливает переменнуюwget_options
в строку, содержащую пробел. На данный момент нет способа узнать, должно ли пространство быть частью опции или разделителем между опциями.Когда вы обращаетесь к переменной с подстановкой в кавычках
wget "$wget_options"
, значение переменной используется как строка. Это означает, что он передается как один параметрwget
, так что это единственный параметр. Это ломает в вашем случае, потому что вы хотели, чтобы это означало несколько вариантов.При использовании подстановки без кавычек
wget $wget_options
значение строковой переменной подвергается процессу расширения с именем «split + glob»:$IFS
переменную). Это приводит к промежуточному списку строк.Это работает в вашем примере, потому что процесс разделения превращает пробел в разделитель, но в целом не работает, так как опция может содержать пробелы и символы подстановки.
В ksh, bash, yash и zsh вы можете использовать переменную массива. Массив в терминологии оболочки представляет собой список строк, поэтому нет потери информации. Чтобы создать переменную массива, поместите скобки вокруг элементов массива при назначении значения переменной. Чтобы получить доступ ко всем элементам массива, используйте - это обобщение , которое формирует список из элементов массива. Обратите внимание, что здесь вам также нужны двойные кавычки, в противном случае каждый элемент подвергается split + glob.
"${VARIABLE[@]}"
"$@"
В обычном sh нет переменных массива. Если вы не возражаете против потери позиционных аргументов, вы можете использовать их для хранения одного списка строк.
Для получения дополнительной информации см. Почему мой сценарий оболочки задыхается от пробелов или других специальных символов?
источник
(set -- ...; exec wget "$@" ...)
.