AABS

Вступление.

Утилита sed никогда не используется в одиночку, её использование очень тесно связано с использованием оболочки. Я использую bash, и потому про эту оболочку также стоит сказать несколько слов.

К счастью, про bash уже многое написано, вы можете почитать man bash, а если вам этого недостаточно, то и info bash. Кроме того, имеется Advanced Bash Scripting (сокращённо - ABS), в котором расписаны многие тонкие моменты кодинга на этом ЯП.

Замечание

Кстати, ABS доступно и в русском переводе. Время от времени я просматриваю этот перевод, и даже иногда отписываюсь о найденных ошибках и неточностях. К счастью, переводчики ABS очень внимательны, и никогда не позволяют себе разных вольностей, вроде тех, что позволяю себе я (например в моём переводе info sed полно совсем левых комментариев и откровенной отсебятины. А что делать? Ну читайте оригинал...). Конечно перед прочтением этого документа следует внимательно изучить и man bash и ABS, про многие вещи я просто не буду писать - ведь они и без меня уже подробно описаны.

Тем не менее, даже в ABS некоторые моменты не раскрыты. Это и заставило меня написать данное руководство.

Разбор строки утилитой bash.

Прежде всего, стоит заметить, что утилита bash на самом деле обрабатывает текстовый поток. Причём построчно (как и sed), разбор каждой загруженной строки прост:

  1. Прежде всего, строка делится на слова, т.е. на последовательности любых символов, в которых нет разделителей. Разделители записаны в переменной $IFS, и доступны для редактирования. Конечно просто так просмотреть эту переменную невозможно - все разделители невидимы, однако мы можем воспользоваться командой hexdump:
    $ echo -e "$IFS" | hexdump -c
    0000000      \t  \n  \n
    0000004
    Эта команда вывела нам список разделителей - как видно, у меня установлено три разделителя, это пробел, табуляция и перевод строки (вообще-то переводов строки 2 шт, второй был добавлен командой echo).

    Замечание

    Можно так-же задавать слова и с разделителями - для этого нужно либо экранировать разделители обратным слешем ('\'), либо заключать слова в двойные или одиночные кавычки ('"' или ''') см. ниже.
  2. Т.о. у нас получается набор слов. Первое слово в строке считается командой, и обрабатывается особым образом: во первых, bash проверяет, есть-ли такая команда в списке встроенных команд. Если есть такая встроенная команда, то она выполняется, в противном случае, bash просматривает каталоги перечисленные в $PATH, в поисках нужного файла. Если данного слова нет, то работа bash прерывается с сообщением об ошибке.

    Внимание

    В отличие от OS Windows, в переменной $PATH нет пути к текущему каталогу. А если он и есть (как в Slackware для юзеров), он записан последним. Это связано с безопасностью - злоумышленник не сможет подменить команду из числа системных. К примеру, злоумышленник мог-бы создать свой исполняемый файл su, который отправил-бы ему введённый нами пароль рута например на его (злоумышленника) адрес электронной почты, а затем выполнил-бы родную su. Это невозможно по той причине, что будет выполнена родная команда su, по той причине, что именно она лежит в одном из первых путей в $PATH.

    Замечание

    Кроме того, злоумышленник не сможет так-же выполнить и родную команду su в своём скрипте - дело в том, что su читает пароли исключительно с клавиатуры, и попытка подсунуть ей пароль как-то иначе приведёт только к ошибке.
    Из-за того, что в $PATH нет нашего текущего каталога, нам приходится запускать наши скрипты с указанием пути, например так:
    ./my_script
    Конечно при этом необходимо установить право исполнения для этого файла, командой
    chmod +x my_script

    Внимание

    Следует учитывать, что файлы ВСЕГДА создаются со сброшенным правом исполнения. Это тоже связано с безопасностью: злоумышленник не сможет нас заставить выполнить свою команду, если мы только явно не пропишем право её исполнения.

    Если вы хотите ещё большей защиты, то вы можете монтировать раздел /home с опцией noexec, это приведёт к тому, что НИКТО не сможет выполнить файлы в данном разделе (а создание файлов во всех прочих разделах запрещено. Конечно необходимо запретить создание исполняемых файлов в общедоступных директориях /tmp и /var/tmp).

    Однако, не следует забывать, что любой пользователь может выполнить любой скрипт подсунув его как параметр утилите bash. Для этого права выполнения НЕ нужно. Достаточно права выполнения самой bash (которое всегда есть), и права чтения этого скрипта.

Разбор остальных параметров.

После разделения строки на слова, выполняется первое слово как команда. Все остальные слова считаются параметрами. Из bash скрипта к ним можно получить доступ с помощью конструкций $0, $1, $2 и т.д.. Все целиком можно получить конструкцией $@ (в эту конструкцию НЕ входит $0, т.е. имя самой команды).

Глоббинг

Однако, разбор командной строки вовсе не завершается после разделения её на слова. Если в некоторых словах имеются символы *, {, }, ?, [, и ], то это слово считается именем файла.

Звёздочка.

Вместо звёздочки (*) подставляется любое количество любых файлов. К примеру, если у нас в текущем каталоге имеются файлы

1.html

2.html

3.html

То, команда xxx *.html получит не один параметр, а 3. Данная команда выполнится так-же, как если-бы мы явно записали xxx 1.html 2.html 3.html. Если файлов подходящих под шаблон нет, то команда получит сам шаблон в неизменном виде.

Предостережение

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

Звёздочка раскрывает любые файлы, кроме скрытых (скрытыми файлами и каталогами считаются любые файлы, первым символом в которых стоит точка (.)). Для вывода скрытых файлов следует использовать следующее выражение: .*. Некоторые утилиты так-же позволяют обрабатывать скрытые файлы путём указания опций. Например, утилита ls показывает скрытые файлы с опцией -a.

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

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

Конечно, если у вас нет в этом каталоге обычных файлов (скрытые файлы всегда есть, это '.' и '..'), то вторая звёздочка не раскроется, и будет передана утилите xxx без изменений. На что она может выругаться, типа «Нет такого файла '*'».

Замечание

В ОС Linux нет разницы между файлами и каталогами, точнее, каталоги являются обычными файлами. По этой причине не существует простого способа раскрыть только файлы (не каталоги), или только каталоги. Впрочем, часто это и не нужно - например вы хотите передать в свою утилиту только файлы с музыкой - нет ничего проще:
xxx *.mp3
Именно по этой причине, я и рекомендую задавать всем файлам осмысленные расширения - да, они не нужны для ОС (в отличие от M$-DOS), однако очень помогут нам.

Предостережение

Нужно отметить, что звёздочки раскрываются вовсе не в простой список файлов, а в отсортированный список. Часто это очень важно. Кроме того, я хотел-бы отметить, что в некоторых случаях раскрытие звёздочки невозможно, по той причине, что файлов слишком много, и они не влезают в строку (ага. Её длинна ограничена). Самое печальное, что при этом НЕ выводится никаких сообщений об ошибках. Файлы просто тупо теряются.

Двойная звёздочка.

В отличие от одиночной звёздочки (которая раскрывается в список файлов), двойная звёздочка раскрывается в список каталогов. К примеру, xxx **/*.sed приведёт к тому, что в утилиту xxx передадутся имена всех sed-скриптов во всех подкаталогах текущего каталога. При этом sed-скрипты из текущего каталога НЕ будут переданы в эту утилиту.

Кроме того, файлы из подкаталогов в подкаталогах так-же не будут переданы. Действие двойной звёздочки аналогично команде find -maxdepth 2 -mindepth 2, только двойная звёздочка ещё и сортирует файлы и не показывает скрытые каталоги.

Вопросительный знак (?).
Этот символ при глоббинге расширяется в любой символ. В отличие от звёздочки, символ должен быть единственным. Т.о., запись xxx ???? передаст в утилиту xxx все файлы из 4х символов. При этом, также как и при использовании звёздочки, скрытые файлы не будут переданы.
Символьные классы.
Как и в RE, при глоббинге допустимо использовать символьные классы. Например, в Linux различается регистр букв, и что-бы найти все картинки приходится использовать такое выражение:
xxx *.[jJ][pP][gG]
оно совпадает и с файлами вроде 1.jpg, и с 1.JPG, т.е. является регистронезависимым. Диапазоны так-же допускаются, к примеру
xxx *.[0-9]
расширяется в любые файлы, которые имеют расширения из любой цифры.

Замечание

Хотя символьные классы и реализованы в глоббинге, но там нет других фичь обычных RE, потому нельзя например задать любое числовое расширение (несколько цифр после точки). Если это необходимо, то можно использовать следующие подходы:
  1. Записать явно все суффиксы, например, для файлов с номерами не больше 999 это можно сделать так:
    xxx *.[0-9] *.[0-9][0-9] *.[0-9][0-9][0-9]
  2. Можно получить список файлов утилитой ls, и прогнать его затем через sed.
  3. Кроме того, можно воспользоваться утилитой find с ключом -regex.
Фигурные скобки {}.
Эти скобки задают альтернативы при глоббинге, к примеру
ls *.{info,txt,sed}
выведет все файлы с расширением info, txt, и sed.

Перенаправление в bash'е.

Команды в bash обычно принимают параметры из стандартного потока ввода (/dev/stdin) и отправляют результат своей работы в стандартный поток вывода (/dev/stdout).

Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).

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