Расширение с помощью * .txt в оболочке не работает, если нет файла .txt

10

Я играл с расширением, и я заметил своеобразное поведение. Я пытался сделать:

echo ./*.txt

И у меня не было никакого файла .txt в моем текущем каталоге. Вывод, который я получил, был:

./*.txt

Мне просто любопытно: почему я это получил? Я ожидал не получить никакого вывода.

PS: когда у меня был .txtфайл, расширение было правильно истолковано. Другими словами, скажем, у меня был файл, smthn.txtэхо фактически повторилось current_directory/smthn.txt.

Невежественный Странник
источник

Ответы:

15

Адаптация из справочной страницы оболочки bash,

bash сканирует каждое слово на наличие символов *,? и [. Если появляется один из этих символов, слово считается шаблоном и заменяется отсортированным по алфавиту списком имен файлов, соответствующих шаблону. Если не найдено подходящих имен файлов и опция оболочки nullglob не включена, слово остается без изменений. Если установлена ​​опция nullglob и совпадений не найдено, слово удаляется.

В этом случае я предполагаю, что nullglob не включен, поэтому слово остается неизменным - отсюда вывод, который вы видите.

ColinB
источник
2
Вы можете изменить поведение следующим образом: shopt -s nullglobвыдаст пустые строки для несопоставленных шаблонов и shopt -u nullglob(стандартная настройка) выдаст сам шаблон.
PerlDuck
13

Я ожидал не получить никакого вывода.

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

Предположим, что вы включили nullglob( shopt -s nullglob), и вы находитесь в каталоге, где не найдено ни одного файла *.txt. Тогда *.txtдействительно расширится до нуля - не пустое поле, а вообще никаких полей - как вы и ожидали. Но это будет иметь следующие результаты:

  • ls *.txtбудет перечислять все файлы в текущем каталоге (кроме скрытых файлов), потому что это то, что lsпроисходит, когда вы не передаете ему аргументы имени файла.
  • cat *.txtбудет читать со стандартного ввода , потому что, когда catнет аргументов имени файла, это как если бы вы работали cat -. Если он работает в интерактивном режиме, он сидит в ожидании ввода. Многие команды ведут себя таким образом.
  • cp *.txt dest/потерпит неудачу с ошибкой cp: missing destination file operand after 'dest/'. Это не катастрофа, но это сбивает с толку и весьма отличается от молчаливого успеха, который, вероятно, желателен.
  • file *.txtи различные другие программы, не имеющие специального поведения для случая нулевых аргументов имени файла, все равно будут с ошибкой или сообщением об использовании, если ни одна из них не будет передана.
  • Даже случаи, которые интуитивно чувствуют, что они должны работать часто, не будут. printf 'Got file: "%s"\n' *.txtбудет печатать Got file: ""вместо ничего.
  • Случайное отказ процитировать вхождения *, ?и [что не предполагается расширить оболочку бы чаще производить очевидно неправильные результаты, но таким образом , что может быть трудно понять. Например, если имена файлов в текущем каталоге не начинаются gedit, то apt list gedit*(где это apt list 'gedit*'было задумано) станет просто apt listи перечислит все доступные пакеты.

Так что хорошо, что вы не получаете такое поведение, не запрашивая его. Вероятно, наиболее распространенной практической ситуацией, которая на самом деле упрощается, nullglobявляется for f in *.txt. Смотрите также этот вопрос (с которым связался ответ Сергея Колодяжного ).

Сложнее ответить на вопрос, почему failglob- если ошибка расширения, связанная с тем, что глобус не соответствует ни одному файлу, - не используется по умолчанию в bash. Я считаю, что ответ Сергея Колодяжного фиксирует причину этого, даже не обращаясь к нему напрямую. Сохранение нерасширяющихся глобусов без возникновения ошибки расширения является (возможно, к сожалению) стандартизированным поведением, а также традиционным и, следовательно, ожидаемым поведением. Хотя bash не пытается быть полностью POSIX-совместимым, если он не вызывается с именем shили не передается --posixопция, многие из его вариантов разработки, даже когда не в режиме POSIX, следуют непосредственно за POSIX. Им пришлось выбрать какое-то поведение, и есть недостатки, связанные с несоответствием ожиданиям пользователей.


Я думаю, что это наименее исторически влиятельный аспект этого вопроса, поэтому я сохранил его напоследок ... но стоит упомянуть, что в nullglobповедении есть что-то немного странно концептуальное .

nullglobна первый взгляд кажется элегантным, поскольку синтаксически он рассматривает случай совпадения файлов с нулями ничем не иначе, как случай с одним, двумя или любым другим числом. Команды, которые мы запускаем, для которых глобусы расширяются в аргументы, не склонны рассматривать их одинаково, как подробно описано выше. Но синтаксически это, по крайней мере, кажется правильным, и я думаю, что это мотивация для вашего вопроса.

И, тем не менее, есть еще одно, более тонкое несоответствие, которое nullglobне устраняется - оно на самом деле усиливается. Случай с нулевыми символами глобинга («подстановочные знаки») трактуется совершенно иначе, чем случай с одним, двумя или любым другим числом. Например, с помощью shopt -s nullglob, если ab?d?fфайлы не совпадают, они удаляются; если ab?dфайлы не совпадают, они удаляются; но если abне совпадает ни с одним файлом (то есть, если нет файла, имя которого точно ab), он все равно не удаляется. Конечно, это будет катастрофа, если она будет удалена, потому что она может вообще не ссылаться на существующий файл в текущем каталоге; это может даже не ссылаться на файл. Но это все еще устраняет любую надежду на полную последовательность.

Три поведения, которые предоставляет bash - по умолчанию обработка глобусов, которые не соответствуют ни одному из файлов, как если бы они не были глобусами, и передача их нерасширенными, поведение, которое вы ожидали от них (если вы простите этот странный поворот фразы) как означающий все нули файлов, которые соответствуют match ( nullglob), и безопасное поведение, учитывающее их ошибки ( failglob) - все они представляют разные подходы к неоднозначности, присущей оболочке, и не могут знать, предназначено ли какое-либо конкретное слово быть имя файла. Оболочка выполняет свои расширения без знания того, как конкретные команды, которые вы вызываете с ней, будут обрабатывать свои аргументы.

Это один из многих случаев разделения интересов . В системах, дизайн которых соответствует философии Unix, каждая часть предназначена для того, чтобы делать что-то одно и делать это хорошо . Оболочка обрабатывает текст в команды и аргументы и вызывает те команды, большинство из которых являются внешними по отношению к самой оболочке. Это, как правило, намного приятнее и гибче, чем системы, в которых внешние команды сами отвечают за выполнение этих преобразований (как в случае традиционных командных процессоров в DOS и Windows). Но у этого есть свои случайные недостатки.

Элия ​​Каган
источник
Видимо, bash-4.3.39 (2) не имеет failglob. Так что это не может быть по умолчанию, потому что это не всегда поддерживается.
Руслан
6

Основная причина в том , что это стандартное поведение определяется POSIX - стандарт , который охватывает оболочки языка команд и среди других согласующих вещей шаблон (оболочек , таких как bash, dashоболочки - по умолчанию в Ubuntu /bin/sh, и kshследовать этому стандарту). Из раздела 2.13.3 Шаблоны, используемые для расширения имени файла :

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

Это, конечно, имеет побочный эффект - сопоставление имени файла, которое может быть буквально *.txt. nullglobВариант в bashи zshмогут помочь: если этой опция включена с помощью shopt -s nullglob(и она не включена по умолчанию , которая относится к этому вопросу), то globstar будет расширен пустой строка , если не найдены имен файлов. ksh93имеет собственный усовершенствованный механизм сопоставления с образцом, который достигает того же эффекта~(N)*.txt

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

Сергей Колодяжный
источник