Убунтология

Популярное содержимое

Текстовые замены и подстановки в Bash

 

Автор статьи: Чувак с гранатой

 

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

Внимание! Предполагается, что вы уже знакомы с основами Bash.

Замена тильды

Скорее всего, вы уже встречались с заменой тильды (символ "~"), которую часто используют для быстрого перехода в домашний каталог:

cd ~

Или для указания пути к файлу в домашнем каталоге:

rm ~/test_dir/text_file

В этих примерах интерпретатор Bash заменяет символ тильды на ваш домашний каталог.

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

Следующая команда выведет домашнюю папку пользователя root:

echo ~root

Ещё одно применение тильды - вывод текущей (аналог команды pwd) и предыдущей посещённой папок, для чего требуется ввести после неё знак "+" или "-":

cd / && cd ~
echo -e "Текущая папка:" ~+ "\nПредыдущая посещённая:" ~-

Если текст после символа тильды не является именем пользователя или знаками "+" и "-", то замены не происходит:

echo ~test

Подстановка имён файлов

Думаю, с этой видом подстановки вы тоже не раз сталкивались в повседневной работе.

Легче всего будет разобраться на примере: создадим временную папку и несколько файлов в ней:

touch бок бот ботинок вот кот лот нота рот

Теперь с помощью разных масок будем выводить только нужные нам файлы. Начнём с символа "*", означающего ноль или больше произвольных символов:

ls бот*

Срабатывает подстановка, и выводятся имена файлов, начинающиеся с букв "бот".

Вопросительный знак в маске - это один произвольный символ:

ls ?от

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

А сейчас выберем только те файлы, первая буква в названии которых "б", "в", "к" или "н", а после неё идёт "от":

ls [б,в,к,н]от*

То же самое можно написать и так (ведь буква "в" находится между "б" и "к"):

ls [б-к,н]от*

Или использовав символ "^" или восклицательный знак, обозначающие отрицание:

ls [^л,р]от*
ls [!л,р]от*

Замена выражений в фигурных скобках

Теперь перейдем к более сложному типу замены - раскрытию скобок.

Предположим, что нам нужно создать несколько папок для фотографий, названных по месяцам и годам: "фото_мм_гг". Можно сделать это так:

mkdir фото_01_09 фото_02_09 фото_03_09 фото_04_09 фото_05_09 ... фото_12_09

И вместо многоточия перечислить все 12 (по количеству месяцев) папок. Легко заметить, что в названии меняется только номер месяца, остальная же часть везде одинаковая.

А сейчас сделаем то же самое, но применив раскрытие скобок:

mkdir фото_{01..12}_09

После выполнения этой команды в текущем каталоге будут созданы 12 папок с названиями желаемого нами формата. Итак, мы видим, что вторая команда получилась намного короче первой.

Примечание: в третьей и более ранних версиях Bash (то есть в релизах Ubuntu, вышедших до 9.10) открывающие нули в случае с двоеточием игнорируются, и мы получим папки с названиями: "фото_1_09", "фото_2_09" и т.д. Чтобы обойти это, придётся немного усложнить команду:

mkdir фото_{0{1..9},10,11,12}_09

Разберём, как это работает. Внутри скобок написана последовательность чисел от 1 до 12, к каждому из которых после замены добавляются префикс "фото_" и суффикс "_09", в результате чего мы и получаем названия папок соответственно месяцам.

Префикс и суффикс являются необязательными частями замены, можно, например, создать папки только по названиям месяцев:

mkdir {январь,февраль,март}

Обратите внимание на то, что слова в скобках не разделены пробелами, иначе получится совсем не то, чего мы хотели.

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

mkdir фото_{{01..05},07,{09..12}}_09

Или, если у вас старая версия Bash:

mkdir фото_{0{1..5},07,09,{10..12}}_09

Здесь уже происходит тройная замена. Сначала раскрываются скобки {01..05} и {09..12}, после чего получается {01,02,03,04,05,07,09,10,11,12}. А потом заменяются оставшиеся скобки, и в результате мы получаем 10 нужных нам папок.

Кроме числовых в фигурных скобках можно использовать и буквенные последовательности (буквы должны быть латинскими). Несколько примеров:

echo {1..12}
echo {01..12}
echo {100..95}
echo {a..z}
echo {r..c}

При этом шаг итерации всегда равен одной единице - нельзя вывести, например, только нечётные числа.

Другой практический пример: мы хотим кое-что подправить в файле xorg.conf, но перед этим нам нужно сделать его резервную копию.

Раньше мы выполняли такую команду:

sudo cp /etc/X11/xorg.conf /etc/X11/xorg.conf.bak

Теперь же воспользуемся заменой скобок:

sudo cp /etc/X11/xorg.conf{,.bak}

Так как первый элемент в скобках пустой, в роли копируемого файла берётся сам xorg.conf.

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

sudo cp /etc/X11/xorg.conf.bak /etc/X11/xorg.conf

В этом случае мы либо перезаписали бы файл xorg.conf его копией, либо получили бы уведомление о том, что файла xorg.conf.bak не существует.

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

touch file{1,2,_test}
rm file{?,_test}

Эта команда удалит все созданные файлы.

И последнее: в элементах в фигурных скобках нужно с помощью бэкслеша ("\") экранизировать, то есть маскировать, следующие символы: пробел, запятая, точка с запятой, восклицательный знак, вертикальная черта ("|"), амперсанд ("&"), угловые скобки ("<" и ">"), фигурные скобки и сам бэкслеш. Пример:

echo {пер\ вый,вто\,рой,тре\\тий}

Подстановка значений переменных

С помощью символа "$" в Bash можно подставлять значения переменных окружения, таких как USER, HOME, PWD и др. Имя переменной можно писать либо сразу после "$", либо заключать в фигурные скобки.

echo "Текущий пользователь:" $USER
echo "Домашняя папка:" $HOME
echo "Текущая папка:" $PWD
echo "Предыдущая посещённая папка:" ${OLDPWD}
echo "Текущая оболочка": ${SHELL}
echo "Используемая локаль": ${LANG}

Список всех действующих переменных можно посмотреть командой:

env

Кроме этого, существует так называемая косвенная подстановка, при которой значение написанной переменной используется как имя другой переменной. Например, создадим переменную TEST, присвоим ей значение "USER" и выполним обычную и косвенную подстановки:

TEST=USER
echo "Обычная подстановка:" $TEST
echo "Косвенная подстановка:" ${!TEST}

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

cd / && cd ~
echo $PWD $OLDPWD

Возникает желание переписать эту команду одним из следующих способов:

echo ${,OLD}PWD
echo ${PWD,OLDPWD}

Но в первом случае Bash выдаёт ошибку, а во втором - выводится только значение первой переменной (или тоже ошибка, если установлена старая версия Bash).

Правильной же будет такая команда:

echo {$,$OLD}PWD

Таким образом, при замене фигурных скобок нельзя использовать префикс "$".

Подстановка результатов выполнения команд

Это ещё один тип замены в Bash, использующий символ "$". Она имеет две формы написания:

$(команда)
`команда`

Приведу пример с выводом результата команды uptime:

echo $(uptime)
echo `uptime`

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

echo $(ls -l)

Чтобы этого избежать, надо заключить подстановку в двойные кавычки:

echo "$(ls -l)"

Ну и расскажу про небольшую хитрость при подстановке вывода команды cat. Её можно осуществить так:

echo $(cat text_file)

Но то же самое можно выполнить быстрее (как в смысле набора команды, так и времени выполнения):

echo $(< text_file)

 

Подстановка значений арифметических выражений

И снова замена с использованием символа "$" - вместо арифметических выражений в Bash можно подставлять их результаты (здесь тоже две формы ввода):

$((выражение))
$[выражение]

В выражениях можно использовать следующие операции (перечислено по убыванию приоритета):

    пост-инкремент ("имя++") и пост-декремент ("имя--");
    пре-инкремент ("++имя") и пре-декремент ("--имя");
    унарные плюс ("+") и минус ("-");
    логическое ("!") и побитовое ("~") отрицания;
    возведение в степень ("**");
    умножение ("*"), целочисленное деление ("/") и остаток от него ("%");
    сложение ("+") и вычитание ("-");
    побитовые сдвиги влево ("<<") и вправо (">>");
    сравнения (">=", "<=", ">", "<");
    равенство ("==") и неравенство ("!=");
    побитовое "И" ("&");
    побитовое исключающее "ИЛИ" ("^");
    побитовое "ИЛИ" ("|");
    логическое "И" ("&&");
    логическое "ИЛИ" ("||");
    условное выражение ("условие?значение1:значение2");
    присваивания ("=", "*=", "/=", "%=", "+=", "-=");
    запятая ("выражение1,выражение2").

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

Приведу несколько примеров:

echo $((3+4))
echo $(( 10 / 2 - 3 * -1 ))
echo $[11%2]
echo $[2**3 == 8]
echo $[ 4 >> 1 ]
echo $[1 + $[!1]]
echo $[ 3 + 1 && $[!0] ]
echo $[ 5 > 2 ? 1 : 0 ]
echo $[ 1 + 1, 1 - 1]

Операцию логического отрицания (шестой пример) лучше всегда заключать в отдельное выражение, так как иначе Bash может неправильно интерпретировать символ "!" и посчитать это за ошибку.

Для операций с числами, представленных в системах счисления, отличных от десятичной, используется такая запись:

основание#число

Основание может быть любым десятичным числом в диапазоне от 2 до 64.

К примеру, логическое "И" чисел 6 и 3 в этом случае будет выглядеть так:

echo $[ 2#110 & 2#11 ]

Для шестнадцетиричных и восьмеричных чисел существует собственная запись:

echo "Пример с шестнадцетиричными числами: " $[ 0xA + 0X5 ]
echo "Пример с восьмеричными числами: " $[ 010 + 02 ]

В первом примере не имеет значения, используется ли строчный символ "x" или прописной "X".

Также в выражения можно подставлять значения переменных:

A=1 && B=2
echo $[ $A + $B ]
echo $[ (A = 10) / (B += 2) ]
echo "A = " $A ", B = " $B

И результаты выполнения команд:

date +%Y
echo $[ $(date +%Y) - 1 ]

Подстановка процессов

В следующем примере нам нужно вывести все файлы и папки, содержащие подстроку "doc":

ls -l | grep doc

Это же можно сделать, применив подстановку процессов:

grep doc <(ls -l)

Здесь символ "<" означает создание временного файла типа "/dev/fd/xxx", в который будет перенаправлен вывод команды ls -l, и подстановку его имени вместо этой команды. После этого grep doc принимает имя файла и отбирает из его содержимого нужные строки.

Теперь выполним предыдущую команду наоборот, создав временный файл для перенаправления ввода:

ls -l > >(grep doc)

В этом примере вывод ls -l перенаправляется в созданный временный файл, а команда grep doc принимает данные из этого файла.

И практический пример: нам нужно вывести отсортированное содержимое двух папок (только имена файлов) и одного текстового файла. Для начала создадим их (советую сделать это в отделной пустой папке):

mkdir documents && touch documents/file{1,3,5}
mkdir images && touch images/file{2,4}
echo -e "word1\nword2\nword3" > words

Дальше можно поступить и так:

echo -e "$(ls documents)" "\n$(ls images)" "\n$(less words)" | sort

Но вариант с применением подстановки процессов проще:

sort <(ls documents) <(ls images) <(less words)

Разбиение слов

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

Создадим переменную TEST:

TEST=1@2_3@4_5@6
echo $TEST

После этого присвоим следующее значение переменной IFS (Internal Field Separator), которая отвечает за разделение слов на части, и выведем значение переменной TEST:

IFS="@_"
echo $TEST

Как мы видим, символы "@" и "_" стали разделителями, и теперь заменяются пробелами.

Чтобы вывести значение TEST без разбиения, заключим её имя в двойные кавычки:

echo "$TEST"

И наконец, восстановим значение переменной IFS по умолчанию:

IFS=""
echo $TEST

Порядок выполнения замен и подстановок

Ну и последнее, про что надо знать - замены и подстановки тоже имеют свой порядок приоритетов:

    первыми выполняются замены выражений в фигурных скобках;
    затем замены тильды;
    подстановки значений переменных, результатов выражений и выполнения команд и подстановки процессов;
    разбиения слов;
    и последними - подстановки имён файлов.

-----
Спасибо sKwa за примеры и замечания.