Почему nullglob не используется по умолчанию?

61

В большинстве оболочек nullglobне по умолчанию. Это означает, например, если вы запустите эту команду

ls *

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

Итак, есть ли причина, по которой nullglobотключено по умолчанию? Если так, то в чем причина?

Dakkaron
источник
13
Кто-то однажды сделал плохой выбор, который превратился в «мы всегда так делали». Очень распространенное явление (не только) в мире программного обеспечения.
PSkocik
4
Первоначальная причина в том, что опция nullglob тогда еще не существовала. Поэтому для обеспечения обратной совместимости он должен быть отключен по умолчанию.
PM 2Ring
3
Несмотря на то, что в нем конкретно не упоминается нулевой глобус, в этом ответе содержится
StrongBad
4
У меня создается впечатление, что некоторые думают, что должно быть очевидно, что nullglob должен быть включен по умолчанию. Я не думаю, что это очевидно. То, что расширения происходят в оболочке перед вызовом команды, означает, что расширение до нуля является менее интуитивным поведением, чем глобус, оставшийся неизменным.
Кодзиро
2
@kojiro Менее понятен для кого? Любой, кто знаком с оболочками * NIX, знает, что *это глобус и распространяется на все существующие файлы; как это «интуитивно» для особого случая, когда пустые глобусы каталогов «расширяются» до литерала *?
Кайл Стрэнд,

Ответы:

78

nullglobВариант (который кстати является zshизобретением, только добавил года спустя bash( 2.0)) , не было бы идеальными в ряде случаев. И lsхороший пример:

ls *.txt

Или его более правильный эквивалент:

ls -- *.txt

С nullglobon будет работать lsбез аргумента, который рассматривается как ls -- .(перечислить текущий каталог), если не найдено ни одного файла, что, вероятно, хуже, чем вызов lsс литералом в *.txtкачестве аргумента.

У вас будут похожие проблемы с большинством текстовых утилит:

grep foo *.txt

Будет искать fooна стандартный ввод, если нет txtфайла.

Более разумное значение по умолчанию - csh, tcsh, zsh или fish 2.3+ (и ранних оболочек Unix) - вообще отменить команду, если глобус не совпадает.

bash(начиная с версии 3) имеет failglobопции для этого (интересно к этой дискуссии, так как вопреки ash, AT & T kshили zsh, bashне поддерживает локальные области для вариантов (хотя это изменить в 4.4), что вариант , когда включен глобально делает перерыв несколько вещей как функции завершения bash).

Обратите внимание , что CSH и Tcsh немного отличается от zsh, fishили bash -O failglobв таких случаях , как:

ls -- *.txt *.html

Где вам нужно, чтобы все шарики не совпадали, чтобы команда была отменена. Например, если есть один текстовый файл и нет HTML-файла, это становится:

ls -- file.txt

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

ls -- *.(txt|html)

В zshand ksh93вы также можете применять nullglob для каждого отдельного глобуса, что намного более разумно, чем изменение глобального параметра:

files=(*.txt(N))  # zsh
files=(~(N)*.txt) # ksh93

создаст пустой массив, если нет txtфайла, вместо сбоя команды с ошибкой (или создания массива с одним *.txtлитеральным аргументом с другими оболочками).

Версии fishдо 2.3 работали бы как, bash -O nullglobно выдают предупреждение, когда интерактивен, когда у шарика нет соответствия. Начиная с 2.3, он работает как zshза исключением глобусов, используемых в for, setили count.

Теперь, на заметку истории, поведение было фактически нарушено оболочкой Борна. В предыдущих версиях Unix глобирование выполнялось через /etc/globпомощника, и этот помощник вел себя так csh: команда не выполнит команду, если ни один из глобусов не соответствует ни одному файлу, и удалит глобусы без совпадения в противном случае.

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

Обратите внимание, что оболочка Bourne (и оболочка C) поставляется с еще одной новой функцией Unix: средой. Это означало , что переменная расширение (это предшественник только имел $1, $2... позиционные параметры). Оболочка Bourne также ввела подстановку команд.

Еще одно неудачное решение для оболочки Bourne заключалось в том, чтобы выполнить сглаживание (и разбиение) при расширении переменных и подстановке команд (возможно, для обратной совместимости с оболочкой Томпсона, где echo $1все равно будет выполняться вызов, /etc/globесли он $1содержит подстановочные знаки (это больше похоже на расширение макроса препроцессора). там же, как и в расширенном значении, снова был разобран код оболочки)).

Неудачные глобусы, которые не совпадают, означают, например, что:

pattern='a.*b'
grep $pattern file

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

Стефан Шазелас
источник
Есть еще одна проблема с удобством использования: nullglobкажется, что она нарушает завершение табуляции (нажатие клавиши табуляции ничего не делает, когда она включена).
Кайл Стрэнд
1
Можно использовать nullglob для каждого глобуса с bash - хотя синтаксис не такой элегантный, как у zshfiles=$(shopt -s nullglob;echo *.txt)
Jon Nalley
2
@JonNalley, который хранит конкатенацию (с пробелом) имен файлов (с возможным преобразованием с xpg_echo) в скалярные переменные. Вы должны были бы что - то подобное readarray -td '' files < <(shopt -s nullglob; printf '%s\0' *.txt)с bash4.4 или выше , или (shopt -s nullglob; printf '%s\0' *.txt) | xargs -r0 cmdс GNU xargsдля того , чтобы можно было использовать на все с произвольными именами файлов. Или, все еще с bash4.4, используйте вспомогательную функцию, которая использует local -(скопированный из пепла 25 лет спустя) для локальной области действия для вариантов.
Стефан