Практическое написание сценариев командной оболочки Bash/Bash подстановки

Материал из Викиучебника — открытых книг для открытого мира
Перейти к навигации Перейти к поиску
← Эмуляция ссылочной адресации Глава Команды →
Bash подстановки


Простой подстановкой (expansion) в этой книге мы называем замену обращения к переменной ее значением. Простые подстановки это что-то похожее на обращение к переменной в других языках программирования, но в Bash эта операция отличается следующим:

  • Заменяемая переменная может быть не объявлена: в этом случае мы говорим, что подстановка раскрывается в пустоту. Пустота есть пустота: место в сценарии, в котором прописано раскрытие останется пустым, как будто само раскрытие не было записано в коде сценария.
  • Переменная может быть объявлена, но проинициализирована пустотой. Следует отличать понятия пустоты и пустой строки (т.е. строки с нулевым числом символов): пустая строка в отличие от пустоты может быть проверена на длину; над пустой строкой можно выполнять строковые операции, а над пустотой нельзя.

У простой подстановки есть две формы:

  • Упрощенная. В упрощенной форме достаточно после знака доллара написать имя переменной, например $VARIABLE_NAME.
  • Строгая. В строгой форме имя переменной нужно поместить в фигурные скобки, например ${VARIABLE_NAME}. В таком виде строгая форма ничем не отличается от упрощенной, тем не менее только строгая форма разрешает вам пользоваться встроенными подстановочными операциями — мощным инструментом командной оболочки.

Давайте рассмотрим следующий код.

$EMPTYNESS # Подстановка не объявленной переменной всегда раскрывается в пустоту.
[[ -z $EMPTYNESS ]] && echo "Emptyness expansion" # Опция -z команды test позволяет определять пустую подстановку.

EMPTYNESS= # Если после равно нет какого-то значения, например в форме строки или 'голого слова' (bareword), то она остается пустой.
EMPTYNESS="" # Теперь переменная проинициализирована пустой строкой.

# Для того, чтобы сделать переменную пустой, служит команда unset
unset EMPTYNESS
# или
unset -v EMPTYNESS

Кроме простой подстановки, Bash позволяет делать некоторые более продвинутые, встроенные в интерпретатор:

  • Арифметические подстановки. Позволяет на ходу делать вычисления и подставлять результат.
  • Подстановка с подстановочной операцией.
  • Подстановка результата из подоболочки.
  • Подстановка результата процесса через автоматический файл.
  • Скобочные подстановки. Позволяют быстро генерировать списки, используя комбинаторные методы.
  • Тильда-подстановки. Позволяют делать подстановки относительно залогинившегося пользователя.
  • Globbing-подстановки. Позволяет через маскирующие символы подставлять имена файлов.

Далее мы рассмотрим каждый вид подстановки и ситуации, когда они применяются.

Арифметические подстановки[править]

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

$((<выражение>))

Примеры

N1=2
N2=3
N3=26
N4=7
N5=-8

# Математические вычисления
echo "$N1 + $N2 = $((N1 + N2))"   # сложение
echo "$N1 - $N2 = $((N1 - N2))"   # вычитание
echo "$N1 * $N2 = $((N1 * N2))"   # умножение
echo "$N1 ** $N2 = $((N1 ** N2))" # возведение в степень
echo "$N3 / $N4 = $((N3 / N4))"   # деление
echo "$N3 % $N4 = $((N3 % N4))"   # остаток от деления

# Унарные операторы
echo "-($N5) = $((-N5))"  # унарный минус
echo "+($N5) = $((+N5))"  # унарный плюс

TRUE=0
FALSE=1

# Булевы операторы
echo "!$TRUE = $((!TRUE))"     # отрицание
echo "!$FALSE = $((!FALSE))"
echo "!$FALSE = $((!FALSE))"
echo "$TRUE && $FALSE = $((TRUE && FALSE))"  # конъюнкция
echo "$TRUE || $FALSE = $((TRUE || FALSE))"  # дизъюнкция

NN1=222
NN2=141
# Поразрядные операции
echo "$(echo "obase=2;$NN1" | bc) & $(echo "obase=2;$NN2" | bc) = $(echo "obase=2; $((NN1 & NN2))" | bc)" # поразрядное И
echo "$(echo "obase=2;$NN1" | bc) | $(echo "obase=2;$NN2" | bc) = $(echo "obase=2; $((NN1 | NN2))" | bc)" # поразрядное ИЛИ
echo "$(echo "obase=2;$NN1" | bc) ^ $(echo "obase=2;$NN2" | bc) = $(echo "obase=2; $((NN1 ^ NN2))" | bc)" # поразрядное исключающее ИЛИ
echo "~15 = $((~15))" # поразрядное отрицание

NN1=1
# Поразрядные сдвиги
echo "$(printf "%04d" $(echo "obase=2;$NN1" | bc)) << 3 = $(echo "obase=2; $((NN1 << 3))" | bc)"  # поразрядный сдвиг влево
NN1=8
echo "$(printf "%04d" $(echo "obase=2;$NN1" | bc)) >> 2 = $(printf "%04d" $(echo "obase=2; $((NN1 >> 2))" | bc))" # поразрядный сдвиг вправо

# Инкремент
echo "N1 = $N1"
echo "N1 = ++$N1 = $((++N1))" # префиксный инкремент: увеличить на единицу, присвоить и вывести
echo "N1 = $N1"
echo "N1 = $N1++ = $((N1++))" # постфиксный инкремент: вывести, увеличить на единицу и присвоить
echo "N1 = $N1"

# Декремент
echo "N1 = --$N1 = $((--N1))" # префиксный декремент: уменьшить на единицу, присвоить и вывести
echo "N1 = $N1"
echo "N1 = $N1-- = $((N1--))" # постфиксный декремент: вывести, уменьшить на единицу и присвоить
echo "N1 = $N1"

# Сравнивание чисел
echo "$N1 > $N2 = $((N1 > N2))"
echo "$N1 >= $N2 = $((N1 >= N2))"
echo "$N1 < $N2 = $((N1 <= N2))"
echo "$N1 == $N2 = $((N1 == N2))"
echo "$N1 != $N2 = $((N1 != N2))"

# Тернарная операция
echo "N1 = $N1, N2 = $N2"
echo "$((N1 > N2 ? N1 + N2 : N1 - N2))"

# Несколько выражений в одном
echo "$((N1 = 1 , N += 8, N *= 2, N--))"

# Следующие операторы также имеют форму с присваиванием
# *= /= %= += -= <<= >>= &= ^= |=
echo "N1 = $N1"
echo $(( N1 *= 2 ))
echo $(( N1 /= 3 ))
echo $(( N1 %= 10 ))
echo $(( N1 += 25 ))
echo $(( N1 -= 6 ))
echo $(( N1 <<= 2 ))
echo $(( N1 >>= 3 ))
echo $(( N1 &= 255 ))
echo $(( N1 ^= 255 ))
echo $(( N1 |= 255 ))

Результаты

2 + 3 = 5 
2 - 3 = -1
2 * 3 = 6 
2 ** 3 = 8
26 / 7 = 3
26 % 7 = 5
-(-8) = 8 
+(-8) = -8
!0 = 1    
!1 = 0    
!1 = 0    
0 && 1 = 0
0 || 1 = 1
11011110 & 10001101 = 10001100
11011110 | 10001101 = 11011111
11011110 ^ 10001101 = 1010011
~15 = -16
0001 << 3 = 1000
1000 >> 2 = 0010
N1 = 2
N1 = ++2 = 3
N1 = 3
N1 = 3++ = 3
N1 = 4
N1 = --4 = 3
N1 = 3
N1 = 3-- = 3
N1 = 2
2 > 3 = 0
2 >= 3 = 0
2 < 3 = 1
2 == 3 = 0
2 != 3 = 1
N1 = 2, N2 = 3
-1
16
N1 = 1
2
0
0
25
19
76
9
9
246
255

Подстановочные операции[править]

Подстановочные операции (Shell Parameter Expansion) позволяют преобразовать значение переменной перед подстановкой, при этом не перезаписывая оригинальное значение. Эти операции реализует командная оболочка, поэтому эта техника потенциально не переносима. Раньше разработчикам приходилось вызывать различные утилиты, чтобы некоторым образом преобразовать строку. Со временем большую часть таких рутинных операций перенесли в командную оболочку через подстановки.

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

Операции, связанные с присваиванием
echo ${EMPTYNESS-"NULL"} # Если переменная EMPTYNESS раскрывается в пустоту, то вместо нее будет подставлено значение справа от тире.
                    # Результат: "NULL"
EMPTYNESS=""
echo ${EMPTYNESS:-"EMPTY"} # Если переменная EMPTYNESS раскрывается в пустоту или в строку нулевой длины,
                      # то вместо нее будет подставлено значение справа от :-.
                      # Результат: "EMPTY"

unset -v EMPTYNESS
echo ${EMPTYNESS="DEFAULT_VALUE"} # Если переменная EMPTYNESS раскрывается в пустоту, то вместо нее будет подставлено и ПРИСВОЕНО значение справа от равно.
                                  # Результат: "DEFAULT_VALUE"

unset -v EMPTYNESS
echo ${EMPTYNESS:="DEFAULT_VALUE"} # Если переменная EMPTYNESS раскрывается в пустоту или в строку нулевой длины,
                                   # то вместо нее будет подставлено и ПРИСВОЕНО значение справа от равно.
                                   # Результат: "DEFAULT_VALUE"

EMPTYNESS=""
echo "${EMPTYNESS+"Actually, EMPTYNESS is not NULL."}" # Если переменная не раскрывается в пустоту, то будет подставлена строка справа от +.
                                                       # Результат: "Actually, EMPTYNESS is not NULL."

EMPTYNESS="some text"
echo "${EMPTYNESS:+"Actually, EMPTYNESS is not empty."}" # Если переменная не раскрывается в пустоту, либо в строку нулевой длины,
                                                         # то будет подставлена строка справа от :+.
                                                         # Результат: "Actually, EMPTYNESS is not empty."

unset -v EMPTYNESS
# Следующие две подстановочные операции используются обычно для технологических проверок инициализации переменных.
: ${EMPTYNESS?"is NULL"} # Сценарий будет аварийно прерван с сообщением, указанным после знака вопроса, если переменная раскрывается в пустоту.

: ${EMPTYNESS:?"is NULL"} # Сценарий будет аварийно прерван с сообщением, указанным после :?, если переменная раскрывается в пустоту или в строку нулевой длины.
Работа с регистром символов
TEST_STR="sOmE_Text@123"
echo "(Original test)" ${TEST_STR}

echo "(To upper case)" ${TEST_STR^^}                     # Перевести все символы в верхний регистр
echo "(To upper case for first letter)" ${TEST_STR^}     # Перевести в верхний регистр только первый символ строки

echo "(To lower case)" ${TEST_STR,,}                     # Перевести все символы в нижний регистр
TEST_STR_1=${TEST_STR^^}
echo "(To lower case for first letter)" ${TEST_STR_1,}   # Перевести в нижний регистр только первый символ

echo "(Reverse case for every letter)" ${TEST_STR~~}     # Инвертировать регистр каждого символа в строке
echo "(Reverse case for first letter)" ${TEST_STR~}      # Инвертировать регистр только первого символа

# Вы не ограничены в использовании только простых переменных. Вы можете редактировать также списки, сформированные, например, из массива.
declare -a array=(val1 Val2 vAL3 VAL4 VaL5 vAl6)
echo "${array[@]} original"
echo "${array[@]^^} ^^"
echo "${array[@]^} ^"
echo "${array[@],,} ,,"
echo "${array[@],} ,"
echo "${array[@]~~} ~~"
echo "${array[@]~} ~"
Работа со строками и выделение подстрок
echo "Number of letters in TEST_STR: ${#TEST_STR}" # Возвращает число символов в строке
echo "Substring (from 0 symbol get 5 symbols): ${TEST_STR:0:5}" # Выделяет подстроку из 5 символов, начиная с символа с индексом 0 (т.е. с первого)
echo "Display string from 4th symbol: ${TEST_STR:3}" # Выделяет подстроку, начиная с символа с индексом 3

# Вы можете использовать отрицательные числа, чтобы проходить строку с правого края.
echo "Cut off 3 symbols from right edge: ${TEST_STR:0:-3}" # Отсечь три символа справа

# В следующем примере мы захватываем три последних символа через отступ на три
# символа от правого края. Обратите внимание, что пробел перед минусом обязательный.
echo "Display last 3 symbols: ${TEST_STR: -3:3}"  # Первый способ
echo "Display last 3 symbols: ${TEST_STR:(-3):3}" # Второй способ

# Вы можете работать похожим образом и с элементами массива.
echo "Array size ${#array[@]}" # Выводит размер массива
echo "Size of the second element of the array ${#array[1]}" # Выводит число символов в элементе массива с индексом 1

# Удаление части начальных и конечных символов строки по маске. Очень часто такая возможность используется, чтобы
# выделять части абсолютного пути.
SOME_PATH="/very/long/path/to/archive.tar.gz"
echo "(Original path)  $SOME_PATH"

echo "(Full path to archive) ${SOME_PATH%/*}"              # Результат: "/very/long/path/to"
echo "(File name) ${SOME_PATH##*/}"                        # Результат: "archive.tar.gz"
echo "(Full path without extensions) ${SOME_PATH%%.*}"     # Результат: "/very/long/path/to/archive"
echo "(Full path without gz-extension) ${SOME_PATH%.*}"    # Результат: "/very/long/path/to/archive.tar"
echo "(Most outer extension) ${SOME_PATH##*.}"             # Результат: "gz"
echo "(All extensions) ${SOME_PATH#*.}"                    # Результат: "tar.gz"
echo "(Replace gz by bz2) ${SOME_PATH%.gz}.bz2"            # Результат: "/very/long/path/to/archive.tar.bz2"
Замена подстрок
TEST_STR="Very long long string with spaces."
echo "(Original string) $TEST_STR"

echo "(Replace 'long' by 'short') ${TEST_STR//long/short}"  # Заменить все вхождения 'long' на 'short'
echo "(Replace 'long' by 'short') ${TEST_STR/long/short}"   # Заменить только первое вхождение 'long' на 'short'
echo "(Remove 'long' from the text) ${TEST_STR//long }"     # Удалить все вхождения 'long'. Фактически мы заменяем их строкой нулевой длины
echo "(Remove 'long' from the text) ${TEST_STR/long }"      # Удалить только первое вхождение 'long'
Другие
# Следующие подстановки можно использовать в технологических целях

# Вывести имена всех переменных, которые начинаются на 'TEST'
echo "Display all variables with PREFIX='TEST': ${!TEST*}" # Первый способ
echo "Display all variables with PREFIX='TEST': ${!TEST@}" # Второй способ

Подстановка результата из подоболочки[править]

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

$(<командный список>)

# Со времен Bourne Shell поддерживается также вариант через наклонные апострофы
`командный список`
# Но мы не рекомендуем его использовать в новых сценариях из-за проблем с читаемостью такого синтаксиса,
# а также сложностями вкладывания одних подстановок в другие.

На практике этот прием очень часто используется, чтобы проинициализировать переменную выводом другой команды, либо получить некоторый список (например список файлов через команду find).

Примеры

# Следующий пример вызывает echo в подоболочке, вывод которой будет подставлен и исполнен в текущей оболочке.
$(echo "echo "Hello, world!"")

# Передача результата подоболочки функции аргументами
# В этом примере функция printer просто напечатает свои аргументы, которые формируются
# через вызов команды ls в подоболочке. В данном примере будут выведены все файлы
# в текущем рабочем каталоге.
printer() {
	echo ">>> Called $FUNCNAME"
	local i=0
	for arg; do
		echo "$((++i)) $arg"
	done
}

printer $(ls)

Такая подстановка может вкладываться сколько угодно раз. Вложенная подстановка создает новый уровень подоболочки:

$($($(third-level-command)))
# или
$( # Первый уровень
   $( # Второй уровень
      $(third-level-command)
    )
)
# Здесь сначала исполнится команда третьего уровня подоболочки, чей вывод будет подставлен
# во второй уровень. Вывод третьего уровня должен быть по меньшей мере командой, чтобы второй
# уровень подоболочки смог его исполнить. Затем вывод второго уровня подставится в первый уровень подоболочки.
# Наконец, первый уровень подоболочки попытается исполнить команду, которая вернулась из второго уровня
# и вернуть весь вывод в корневую командную оболочку.

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

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

echo "$(printf $(printf "one two"))"
# Результат: "one"

В предыдущем примере, вместо ожидаемой строки "one two", мы получаем только "one". Здесь накладываются следующие факторы:

  • Особенность работы команды printf, которая может принимать много аргументов: в первом аргументе должна быть форматная строка, а в каждом последующем должна быть подстановка для формата в форматной строке. Таким образом, любые строки, передаваемые в первом аргументе printf всегда должны заключаться в кавычки, если они имеют пробелы или символы поля IFS.
  • Забытые кавычки вокруг второго уровня. Команда printf на втором уровне выполнится правильно и вернет на первый уровень строку "one two". Но, так как самые внешние кавычки не отвечают за первый уровень вложенности, команда printf получит два аргумента из-за разбиения одной строки на два слова: форматную строку one и подстановку для формата two. Так как в форматной строке нет ни одного формата, printf игнорирует все аргументы, начиная со второго.

Обратите внимание, что эту ошибку можно исправить двумя путями:

# Достаточно поставить кавычки вокруг второго уровня вложенности
echo "$(printf "$(printf "one two")")"
# Теперь, когда исполнится второй уровень вложенности, результат будет такой
#
#    "$(printf "$(printf "one two")")" --> "$(printf "one two")"
#              ^-------------------^                 ^-------^
#                        \-------------------------------/
#                               Одни и те же кавычки
#
# Результат: "one two"

# Второй способ заключается в явном указании форматной строки для команды printf первого уровня вложенности
echo "$(printf "%s %s"  $(printf "one two"))"
# В этом случае второй и третий аргумент, которые возвращаются со второго уровня вложенности, будут просто
# подставлены в формат.
#
# Результат: "one two"

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

#!/bin/bash

declare -ar SUBCOMMANDS=("algo1" "algo2" "algo3")

# В этом примере мы формируем описание одной из опций гипотетической утилиты в функции usage().
usage() {
    printf "
    $0      Some utility.

    Options:
        -r $(
        for s in ${SUBCOMMANDS[*]}; do printf "$s|"; done
    )
            <${SUBCOMMANDS[0]}>
                Description of the algorithm 1
            <${SUBCOMMANDS[1]}>
                Description of the algorithm 2
            <${SUBCOMMANDS[2]}>
                Description of the algorithm 3

"
}
usage
# Вывод:
#
#    ./dummy.sh      Some utility.
#
#    Options:
#        -r algo1|algo2|algo3|
#            <algo1>
#                Description of the algorithm 1     
#            <algo2>
#                Description of the algorithm 2     
#            <algo3>
#                Description of the algorithm 3     
#

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

Подстановка результата процесса через автоматический файл[править]

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

В старых версиях командных оболочек такую проблему решали так:

  • Если команда производит данные, то она создает и пишет в некоторый файл, указываемый разработчиком явно. Файл можно создать, если перенаправить стандартный поток вывода команды в файл.
  • Если команда потребляет данные из файла, то разработчик связывает стандартный поток ввода этой команды с дескриптором, ведущим на файл производителя.

Например

#!/bin/bash

PRODUCER_FILE=$(mktemp -p /tmp tmp.XXXXXXXX)

# Для удаления временного файла после завершения сценария
trap "rm -f $PRODUCER_FILE" EXIT SIGKILL

producer() {
    for arg; do
        echo $arg
    done
} >$PRODUCER_FILE

consumer() {
    while read; do
        echo $FUNCNAME: $REPLY
    done
} <$PRODUCER_FILE

producer "alpha" "beta" "gamma"
echo "========================="
cat $PRODUCER_FILE
echo "========================="
consumer
# Результат:
# =========================
# alpha
# beta
# gamma
# =========================
# consumer: alpha
# consumer: beta
# consumer: gamma

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

Дальнейшее развитие интерпретаторов привело к появлению инструмента, называемого конвейером, который связывает стандартный поток вывода команды-производителя со стандартным потоком ввода команды-потребителя через специальный pipe-файл. Жизненным циклом этого файла уже управляет командная оболочка. Таким образом, наш пример можно переписать следующим образом через конвейер:

#!/bin/bash

producer() {
    for arg; do
        echo $arg
    done
}

consumer() {
    while read; do
        echo $FUNCNAME: $REPLY
    done
}

producer "omicron" "kappa" | consumer
# Результат:
# consumer: omicron
# consumer: kappa

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

#!/bin/bash
(
    for arg in "alpha" "beta" "gamma"; do
        echo $arg
    done
) > >(
    while read; do
        echo Anonimous consumer: $REPLY
    done
)
# Результат:
# Anonimous consumer: alpha
# Anonimous consumer: beta
# Anonimous consumer: gamma

Данная конструкция кажется сложной, но поймете ли вы ее смысл, если мы запишем ее в гибридном варианте:

producer "alpha" "beta" "gamma" > >(
    while read; do
        echo Anonimous consumer: $REPLY
    done
)
# Результат:
# Anonimous consumer: alpha
# Anonimous consumer: beta
# Anonimous consumer: gamma
#
# или так

exec 3<><(
    for arg in "alpha" "beta" "gamma"; do
        echo $arg
    done
)

consumer() {
    # Мы используем таймаут, чтобы программа не подвесилась, потому что читаемый файл бесконечный.
    while read -t 1; do
        echo $FUNCNAME: $REPLY
    done
}

consumer 0<&3

# Результат:
# consumer: alpha
# consumer: beta
# consumer: gamma

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

Эта возможность позволяет создать своего рода анонимную функцию и связать ее стандартный ввод или вывод через автоматический pipe-файл. Анонимные функции запускаются асинхронно и передают свой ввод/вывод в специальный файл, назначаемый командной оболочкой. Можно посмотреть что это за файл такой, если написать в командной строке такую конструкцию

$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62

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

Приведем еще один пример

(echo "World")> >(read str; echo "Hello, ${str}")> >(read str1; echo "${str1}! How are you?")
# Результат:
# Hello, World! How are you?
#
# Следующая схема показывает, как такой вывод получается
# echo > [fd 1] > (read; echo) > [fd 2] > (read; echo) > STDOUT

Скобочные подстановки[править]

Скобочные подстановки (Brace Expansion) обычно используются для генерации списков, в которых есть комбинаторные закономерности. Обычно это полезно в циклах for и при заполнении массивом некоторыми данными.

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

Примеры

{element1,element2,element3,element4} # Подставит список "element1 element2 element3 element4"
{0..5} # "0 1 2 3 4 5"
{00..05} # "00 01 02 03 04 05"
{a..z} # Список из всех букв латинского алфавита
1.{0..5} # "1.0 1.1 1.2 1.3 1.4 1.5"
{0..20..2} # Можно генерировать ряд чисел с шагом "0 2 4 6 8 ... 20"
{0000..20..2} # "0000 0002 0004 ... 0020"

# Скобочные подстановки можно вкладывать
{{A..Z},{a..z},{0..9}} # "A B ... Z a b ... z 0 1 ... 9"

# Комбинаторный пример: генерация имен файлов
{john,bill}{0..3}.tar.{bz2,gz}
# john0.tar.bz2
# john0.tar.gz
# john1.tar.bz2
# john1.tar.gz
# ....
# bill0.tar.bz2
# bill0.tar.gz
# ....
# bill3.tar.gz

Тильда-подстановки[править]

Тильда подстановки (Tilde Expansion) позволяют делать подстановки относительно залогинившегося пользователя. Эти подстановки так называются, потому что они начинаются с символа ~.

echo "My home directory is " ~ # Вместо тильды будет подставлен рабочий каталог текущего пользователя.
echo "Current workdir is" ~+ # Будет подставлен текущий рабочий каталог.
echo "Previous workdir is" ~- # Будет подставлен предыдущий рабочий каталог.

# Вообще после '+' и '-' можно указать число. Тогда подставится каталог,
# получаемый командой 'dirs':
#     ~+2    то же что и    dirs +2
#     ~-2    то же что и    dirs -2
# По умолчанию это число равно 0.

echo "Root workdir is" ~root # Заменится на путь к рабочему каталогу пользователя root.
echo "Path to 'my_docs' dir is" ~/my_docs # Заменит подстановку на путь к каталогу my_docs относительно рабочего каталога
                                          # текущего пользователя.
# Каталог my_docs может не существовать в действительности.

Globbing-подстановки[править]

Globbing-подстановки (маскирующие подстановки) позволяют вам подставлять списки из имен файлов, используя маски. Обычно этой возможностью пользователи пользуются в интерактивном режиме, но в сценарии эта возможность также доступна. Как и в интерактивном режиме, эта подстановка разрешается относительно текущего рабочего каталога сценария, если вы не указываете абсолютный/относительный путь в подстановке.

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

  • ?. Маскирует ноль или один литерал маски.
  • *. Маскирует ноль или серию литералов маски.
  • []. Напоминает группу в регулярных выражениях, т.е. сопоставляет один из символов в группе в позиции входящей строки.
  • [^] или [!]. Напоминает инверсную группу в регулярных выражениях.

Рассмотрим примеры.

# Пусть в текущем каталоге лежат файлы: a.txt, b.txt, script.sh и c.tar.gz
echo [a-z].???  # Сформирует список "a.txt b.txt"
echo [a-z]*.??  # "c.tar.gz script.sh"
echo [^a].*     # "b.txt c.tar.gz"
echo /???       # Сформирует список из всех каталогов с трехсимвольным именем в корневом каталоге, например "/bin /cmd /dev /etc /tmp /usr"



← Эмуляция ссылочной адресации Команды →