Глоб символ в переменной расширяется в bash, но не в zsh.

Вопрос или проблема

Я вижу проблему с zsh, где символ глоба внутри переменной не разворачивается так, как я ожидал. Следующий пример лучше объясняет это.

$ echo $0
-bash

$ echo $HOME/Downloads/zsh-test/*
/Users/bruce/Downloads/zsh-test/file1 /Users/bruce/Downloads/zsh-test/file2 /Users/bruce/Downloads/zsh-test/file3 /Users/bruce/Downloads/zsh-test/file4

$ file=*; echo $HOME/Downloads/zsh-test/$file
/Users/bruce/Downloads/zsh-test/file1 /Users/bruce/Downloads/zsh-test/file2 /Users/bruce/Downloads/zsh-test/file3 /Users/bruce/Downloads/zsh-test/file4

Macbook% echo $0
zsh

Macbook% echo $HOME/Downloads/zsh-test/*
/Users/bruce/Downloads/zsh-test/file1 /Users/bruce/Downloads/zsh-test/file2 /Users/bruce/Downloads/zsh-test/file3 /Users/bruce/Downloads/zsh-test/file4

Macbook% file=*; echo $HOME/Downloads/zsh-test/$file
/Users/bruce/Downloads/zsh-test/*

Я ожидал, что последняя команда будет разворачиваться так же, как в bash. Есть идея, что я делаю не так?

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

Большинство людей ожидает

echo $file

что будет выводить содержимое переменной $file, и раздражаются, когда такие оболочки, как bash, этого не делают (это поведение унаследовано от оболочки Bourne, к сожалению, не исправлено ksh и указано в POSIX для интерпретатора sh), и это вызывает много ошибок и уязвимостей в безопасности, и поэтому вам нужно заключать все переменные в кавычки в этих оболочках.

Смотрите, например: Безопасностные последствия забытия заключить переменную в кавычки в bash/POSIX оболочках

Я вижу, что вы тоже этого ожидаете, так как пишете echo $0, а не echo "$0".

zsh это исправил. Он по умолчанию не делает ни глобирования, ни разделения слов при расширении параметров. Вам нужно запросить это явно:

  • echo $=file: выполнить разделение слов
  • echo $~file: выполнить глобирование
  • echo $=~file: выполнить оба

Или вы можете включить опции globsubst и shwordsplit, чтобы получить такое же поведение, как в оболочках, подобных Bourne (эти две опции включены, когда zsh запускается как sh для совместимости с sh), но я бы не рекомендовал это, если вам не нужно, чтобы zsh интерпретировал код, написанный для другой оболочки (и даже в этом случае было бы более логично интерпретировать этот код в эмуляции sh в локальном контексте с emulate -L sh).

Здесь называть вашу переменную file в

file=*

может ввести в заблуждение, если вы намерены, чтобы она была развернута при присваивании¹.

filename_pattern=*

будет более разумным. Если вы хотите переменную, содержащую имена всех ненастроенных файлов в текущем каталоге, вы можете сделать:

files=(*)

или:

files=( *(N) )

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

То есть используйте присваивание переменной массива. Это (file=(*)) будет работать так же, как в bash или ksh93², mksh или yash, кроме того, что zsh не имеет этой другой неудачи оболочки Bourne, при которой шаблон остается неразворачиваемым, когда нет совпадений.


¹ Обратите внимание, что * является совершенно допустимым именем для файла в системе, подобной Unix. Я немного успокаиваюсь, зная, что rm -f -- $file удаляет файл, имя которого хранится в $file, даже если этот файл называется *.

² ksh93 имеет files=( ~(N)* ) как эквивалент files=( *(N) ) zsh, хотя bash и yash, как zsh, также имеют опцию nullglob, которую вы можете включить глобально, чтобы применить это ко всему расширению глобов (хотя это не то, что вам нужно в общем случае).

Ответ или решение

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

Сравнение Bash и Zsh

Расширение переменных и метасимволов

В bash при присвоении метасимвола переменной происходит неявное расширение этого метасимвола при его использовании. Рассмотрим следующий пример:

file=*
echo $HOME/Downloads/zsh-test/$file

В данном случае, при выполнении этой команды в bash, переменная $file расширится до всех файлов в директории, что и ожидается. Однако, когда вы выполняете аналогичный код в zsh:

file=*
echo $HOME/Downloads/zsh-test/$file

Вы получите ; это происходит потому, что zsh, в отличие от bash, не выполняет автоматическую подстановку метасимволов при их использовании в переменных.

Специфика Zsh

Zsh имеет ряд функций, которые обеспечивают более предсказуемое управление переменными. Одной из таких функций является возможность управления расширением переменных через специальный синтаксис:

  • $=file – выполняет разбиение строки по словам.
  • $~file – выполняет подстановку метасимволов.
  • $=~file – выполняет и разбиение, и подстановку метасимволов.

Это поведение может быть совершенно неочевидным для пользователей, привыкших к bash, и именно здесь может возникнуть недопонимание.

Рекомендации

Если вы хотите, чтобы метасимволы корректно обрабатывались в zsh, рассмотрите следующие варианты:

  1. Использование специального синтаксиса:
    Например:

    echo $~file

    В этом случае переменная $file будет обработана как метасимвол, и вы получите ожидаемый результат.

  2. Объявление переменной как массив:
    Использование массивов в zsh позволяет избежать путаницы с синонимами и метасимволами. Например:

    files=(*)
    echo "${files[@]}"

    Это гарантирует, что вы будете работать с массивом файлов, который корректно расширяется.

  3. Изменение настроек оболочки:
    Вы можете активировать опции оболочки, такие как globsubst и shwordsplit. Но стоит отметить, что это может привести к неожиданным результатам, если ваш скрипт рассчитан на поведение, характерное для других оболочек, таких как bash.

Заключение

Понимание различий в расширении переменных и метасимволов между bash и zsh поможет вам избежать распространенных ошибок. Zsh предлагает большую гибкость с помощью своего синтаксиса, что позволяет управлять поведением оболочки более детально. Учитывая эти аспекты, вы сможете использовать мощные возможности zsh более эффективно и избегать досадных недоразумений в процессе работы со скриптами.

Оцените материал
Добавить комментарий

Капча загружается...