Поиск по сайту:

Как правильно разбирать имена файлов в Bash


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

Проблема с правильным анализом имен файлов в Bash

Если вы какое-то время использовали Bash и писали сценарии на его богатом языке Bash, вы, вероятно, сталкивались с некоторыми проблемами синтаксического анализа имен файлов. Давайте рассмотрим простой пример того, что может пойти не так:

touch 'a
> b'

Здесь мы создали файл, в который был введен фактический CR (возврат каретки), нажав клавишу ввода после a. Соглашения об именах файлов Bash очень богаты, и хотя это в некотором смысле круто, мы можем использовать специальные символы, подобные этим, в имени файла, давайте посмотрим, как этот файл работает, когда мы пытаемся выполнить с ним некоторые действия:

ls | xargs rm

Это не сработало. xargs примет ввод из ls (через канал |) и передаст его в rm, но что-то запутался в процессе!

Что пошло не так, так это то, что вывод из ls воспринимается буквально xargs, а «ввод» (CR — возврат каретки) в имени файла рассматривается xargs как фактический символ завершения, а не CR, который должен быть передан в rm, как это должно быть.

Давайте проиллюстрируем это другим способом:

ls | xargs -I{} echo '{}|'

Понятно: xargs обрабатывает ввод как две отдельные строки, разделяя исходное имя файла на две части! Даже если бы мы исправили проблемы с пробелами с помощью какого-нибудь причудливого синтаксического анализа с помощью sed, мы вскоре столкнулись бы с другими проблемами, когда начали использовать другие специальные символы, такие как пробелы, обратную косую черту, кавычки и многое другое!

touch 'a
b'
touch 'a b'
touch 'ab'
touch 'a"b'
touch "a'b"
ls

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

Прежде чем мы углубимся в это, есть еще одна вещь, которую необходимо знать, с которой вы можете столкнуться при анализе вывода ls. Если вы используете цветовое кодирование для списков каталогов, которое включено по умолчанию в Ubuntu, легко столкнуться с другим набором проблем с разбором ls.

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

Чтобы не столкнуться с ними, просто используйте --color=never в качестве опции для ls:
ls --color=никогда.

В Mint 20 (отличная производная операционная система Ubuntu) эта проблема кажется исправленной, хотя проблема может все еще присутствовать во многих других или более старых версиях Ubuntu и т. д. Я видел эту проблему совсем недавно, в середине августа 2020 года в Ubuntu.

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

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

Все еще бывают случаи, когда синтаксический анализ ls выполняется быстро и удобно, но он всегда будет сложным и, вероятно, «грязным», как только будут введены специальные символы, не говоря уже о небезопасности (специальные символы могут использоваться для введения всевозможные проблемы).

Секретный рецепт: завершение NULL

Разработчики инструментов Bash осознали эту же проблему много лет назад и предоставили нам: завершение NULL!

Вы спросите, что такое завершение NULL? Обратите внимание, что в приведенных выше примерах CR (или буквально enter) был основным завершающим символом.

Мы также увидели, как специальные символы, такие как кавычки, пробелы и обратная косая черта, могут использоваться в именах файлов, даже если они имеют специальные функции, когда речь идет о других инструментах анализа и модификации текста Bash, таких как sed. Теперь сравните это с параметром -0 для xargs из man xargs:

-0, –null Элементы ввода завершаются нулевым символом, а не пробелом, а кавычки и обратная косая черта не являются специальными (каждый символ воспринимается буквально). Отключает строку конца файла, которая обрабатывается как любой другой аргумент. Полезно, когда элементы ввода могут содержать пробелы, кавычки или обратную косую черту. Параметр GNU find -print0 создает ввод, подходящий для этого режима.

И опция -print0 для find от man find:

-fprint0 файл Верно; вывести полное имя файла на стандартный вывод, за которым следует нулевой символ (вместо символа новой строки, который использует -print). Это позволяет программам, обрабатывающим результаты поиска, правильно интерпретировать имена файлов, содержащие символы новой строки или другие типы пробелов. Этот параметр соответствует параметру -0 для xargs.

Истинно; здесь означает, что если опция указана, верно следующее;. Также интересны два четких предупреждения, данные в другом месте на той же странице руководства:

  • Если вы передаете вывод find в другую программу и существует малейшая вероятность того, что файлы, которые вы ищете, могут содержать новую строку, то вам следует серьезно подумать об использовании параметра -print0 вместо -print. Информацию о том, как обрабатываются необычные символы в именах файлов, см. в разделе НЕОБЫЧНЫЕ ИМЕНА ФАЙЛОВ.
  • Если вы используете find в скрипте или в ситуации, когда соответствующие файлы могут иметь произвольные имена, вам следует рассмотреть возможность использования -print0 вместо -print.

Эти четкие предупреждения напоминают нам, что синтаксический анализ имен файлов в bash может быть и остается сложной задачей. Однако при правильных параметрах find, а именно -print0, и xargs, а именно -0, все наши специальный символ, содержащий имена файлов, может быть правильно проанализирован:

ls
find . -name 'a*' -print0 
find . -name 'a*' -print0 | xargs -0 ls
find . -name 'a*' -print0 | xargs -0 rm

Сначала мы проверяем наш список каталогов. Там есть все наши имена файлов, содержащие специальные символы. Затем мы делаем простой find ... -print0, чтобы увидеть результат. Обратите внимание, что строки заканчиваются NULLNULL или — тот же символ — не виден).

Мы также отмечаем, что в выводе есть один CR, который соответствует одному CR, который мы ввели в первое имя файла, состоящее из a, затем enter, затем b.

Наконец, вывод не вводит новую строку (также содержащую CR) перед возвращением приглашения терминала $, поскольку строки были NULL, а не CR завершен. Мы нажимаем Enter в командной строке терминала $, чтобы немного прояснить ситуацию.

Затем мы добавляем xargs с параметрами -0, что позволяет xargs правильно обрабатывать ввод с завершением NULL. Мы видим, что ввод, переданный и полученный от ls, выглядит четким, и не происходит никакого искажения преобразования текста.

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

Подведение итогов

Мы видели, насколько важно во многих случаях правильно анализировать и обрабатывать имена файлов в Bash. Хотя научиться правильно использовать find немного сложнее, чем просто использовать ls, преимущества, которые он дает, в конце концов могут окупиться. Повышенная безопасность и отсутствие проблем со специальными символами.

Если вам понравилась эта статья, вы также можете прочитать Как массово переименовывать файлы в числовые имена файлов в Linux, в котором показан интересный и несколько сложный find -print0 | xargs -0 оператор. Наслаждайтесь!