На операционных системах семейства Unix (Linux, FreeBSD и т. п.) в скриптах шелла bash работает команда shift. Когда она выполняется, происходит сдвиг влево позициональных параметров скрипта (т. е. аргументов командной строки, переданных в скрипт). Каждый из параметров помещается в более младшую относительно текущей позицию.
[Описание команды shift]
Когда вы запускаете shift n, текущие позициональные параметры сдвигаются влево n раз. Позициональный параметр x получает значение позиционального параметра x+n. Если параметр x+n не существует, то параметр x становится неустановленным.
Если величина сдвига n для команды shift не указана, то применяется значение по умолчанию 1. Таким образом, команды shift 1 и shift без аргумента делают одно и то же.
Если параметр сдвигается в позицию, которая меньше 1, то он выбрасывается, т. е. его значение теряется. Так что, к примеру, команда shift будет всегда выбрасывать предыдущее значение $1, и shift 2 всегда выбросит предыдущие значения $1 и $2.
Специальный позициональный параметр $0 исключается из операций shift, и он всегда остается доступным, на него команда shift не влияет.
Синтаксис команды shift:
shift[n]
Параметры. Команда shift принимает только 1 аргумент:
n
Количество позиций, на которое параметры должны быть сдвинуты влево. Это значение может быть любым неотрицательным целым числом. Если для n указан ноль (0), то никакой сдвиг параметров не производится. Значение по умолчанию для n (если параметр n не указан) равно 1.
n Количество позиций, на которое параметры должны быть сдвинуты влево. Это значение может быть любым не отрицательным целым числом. Если для n указан ноль (0), то никакой сдвиг параметров не производится. Значение по умолчанию для n (если параметр n не указан) равно 1.
Возвращаемый статус: при успешном завершении команды shift она возвратит значение 0 (что соответствует отсутствию ошибок). В противном случае, если n отрицательное, или n больше, чем текущее количество позициональных параметров, то будет возвращено ненулевое значение.
При исполнении команды Linux (в том числе скрипта bash) в её переменные окружения добавляются параметры командной строки, которые были указаны при запуске скрипта. Имя этих специальных переменных состоят из символа $ и цифры, значение которых соответствует позиции параметра в командной строке (поэтому параметры называют позициональными).
Для примера рассмотрим команду:
$ mv file_old.txt file_new.txt
У этой команды есть 3 позициональных параметра $0, $1 и $2:
Параметр
Позиция
Описание
mv
0
Сама команда mv, которая перемещает файлы. В этом примере команда переименовывает файл file_old.txt в файл file_new.txt.
file_old.txt
1
Оригинальное имя файла.
file_new.txt
2
Новое имя файла.
Первый параметр 0 содержит имя команды. Если для команды не было предоставлено аргументов, то это будет единственный позициональный параметр.
Когда для команды были указаны параметры, каждый из них сохраняется в массив строк с позициональными индексами 1, 2, и т. д. В скрипте к этим переменным можно получить значение по имени, составленному из символа доллара $ и значения индекса параметра (1, 2, и т. д.). Таким образом, значение позиционального параметра 1 доступно через значение переменной $1, значение параметра 2 можно получить из переменной $2, и т. д.
К параметрам с числом 10 или больше можно обратиться, заключив число в фигурные скобки, например ${10}, ${11}, или ${12345}.
Bash отслеживает общее количество позициональных параметров. Это количество сохраняется в специальной переменной окружения $#.
Каждый раз при выполнении команды shift значение $# уменьшается на n (n параметр команды shift или 1, если команда shift запущена без параметров).
Специальный позициональный параметр 0. Этот параметр содержит имя команды, которая была использована для запуска текущего процесса. Таким образом, вы всегда можете узнать через этот параметр имя запущенной команды.
Например, в приглашении команд шелла bash вы можете запустить echo $0, чтобы увидеть команду, которая была запущена в текущей сессии bash:
$ echo $0
/bin/bash
Результат команды echo $0 показывает место расположения исполняемого файла bash.
Есть несколько способов запустить выполняемый файл. Однако следует иметь в виду, что многие программы запускаются через псевдонимы symlink (символическая ссылка, symbolic link). Если вы запустите команду с использованием symlink, то $0 будет содержать имя используемой ссылки.
Например, вы можете создать символическую ссылку на bash в своей домашней директории ~:
$ ln -s /bin/bash ~/mybash
После этого сделайте symlink выполняемой с помощью команды chmod:
$ chmod u+x ~/mybash
Затем вы можете запустить новый дочерний процесс bash выполнением symlink:
$ ~/mybash
Запустится новая командная строка шелла bash, внутри предыдущего шелла. Если вы теперь запустите echo $0, то увидите разницу:
$ echo $0
/home/домен/пользователь/mybash
Эта команда показала полный путь до запущенной команды, в котором псевдоним ~ (тильда) в пути был развернут до реального, начинающегося от корня системы /. Если теперь выполнить команду exit, то произойдет возврат в ваш изначальный шелл.
$ exit
$ rm ~/mybash
Примечание: для программы, написанной на языке C, к позициональным параметрам обращаются через параметры argc и argv, где argv это массив указателей на строки, а argc это целое число, равное количеству элементов в массиве. Значение argc всегда больше или равно 1, и значение argv[0] всегда соответствует позициональному параметру $0, т. е. имени запущенной команды.
intmain (int argc, char**argv)
{
...
[Примеры команды shift]
Создадим простой скрипт, который принимает аргументы. Так мы на практике увидим, как аргументы сохраняются в окружении в виде позициональных параметров, и как shift на них влияет. Для создания скрипта используйте любой редактор, например vim, gedit или mcedit, после чего с помощью команды chmod u+x имяскрипта поменяйте его атрибуты, разрещающие запуск.
#!/bin/bash
shift 0
echo 0: $0
echo 1: $1
echo 2: $2
echo 3: $3
echo 4: $4
Первая строка скрипта содержит "магическую" строчку #!/bin/bash, которая определяет интерпретатор скрипта (программу шелла), который должен быть использован для обработки команд скрипта.
Вторая строка shift 0 пока ничего не делает. Позже мы поменяем 0 на другое значение, чтобы понять, как работает команда shift.
Предположим, что мы создали новый файл скрипта с именем myargs.sh. Сделаем его исполняемым:
$ chmod u+x ./myargs.sh
После этого запустим, указав для него пять аргументов:
$ ./myargs.sh one two three four five
0: ./myargs.sh
1: one
2: two
3: three
4: four
В этом выводе видны значения позициональных параметров 0 .. 4. Значение параметра $5 равно "five", однако в скрипте оно не используется.
Указанные в команде параметры относятся только к текущей запускаемой команде - нашему скрипту myargs.sh. После завершения команды (скрипта) они теряются, вернее откатываются к своему изначальному (в нашем примере пустому) значению, которое было до запуска команды. Вы можете запустить команду:
$ echo $0, $1, $2, $3, $4.
/bin/bash, , , , .
Здесь отображены значение переменных вашей текущей сессии bash. $0 это место размещения исполняемого кода bash, а у остальных параметров $1 .. $4 значения отсутствуют.
Давайте теперь поменяем shift 0 на shift 1:
#!/bin/bash
shift# то же самое, что shift 1
echo 1: $1
echo 2: $2
echo 3: $3
echo 4: $4
Примечание: все, что указано после символа "#", считается комментарием, и игнорируется при запуске скрипта.
Теперь при запуске той же самой команды мы увидим следующее:
$ ./myargs.sh one two three four five
0: ./myargs.sh
1: two
2: three
3: four
4: five
Обратите внимание, что позиция всех параметров была сдвинута на 1. Оригинальное значение $1 было выброшено, и значение $5 теперь стало значением $4. Значение $0 не поменялось.
Однако простой сдвиг на единицу не особо полезен. Обычно shift запускают несколько раз в цикле, что удобно для выполнения одинаковых повторяющихся действий над параметрами (мы это рассмотрим далее на практическом примере). Для цикла скрипт можно переписать следующим образом:
Обратите внимание, что здесь shift это последняя команда в теле цикла. Это позволяет выполнить какую-либо обработку со всеми начальными параметрами до того, как будет произведен самый первый сдвиг.
Запустим наш скрипт снова. Он прокрутится 5 раз (i будет последовательно увеличиваться от 0 до 4). С каждой итерацией тела цикла shift будет делать сдвиг позициональных параметров влево, с выводом в терминал значений переменных $1 .. $4.
$ ./myargs.sh one two three four five
Shifted 0 time(s):
------------------
1: one
2: two
3: three
4: four
Shifted 1 time(s):
------------------
1: two
2: three
3: four
4: five
Shifted 2 time(s):
------------------
1: three
2: four
3: five
4:
Shifted 3 time(s):
------------------
1: four
2: five
3:
4:
Shifted 4 time(s):
------------------
1: five
2:
3:
4:
Практический пример использования shift. Следующий скрипт clean-old-files.sh принимает в командной строке список имен директорий. Он сканирует каждую из них, проверяя дату модификации находящихся там файлов. Если к каким-либо файлам не было обращения больше одного года, то они удаляются.
#!/bin/bash
# Сканирование директорий на наличие старых файлов (к которым не было обращения
# более 365 дней) и удаление их.
USAGE="Usage: $0 dir1 dir2 dir3 ..."
if["$#"=="0"]; then# Если не было предоставлено аргументов,echo"Error: no directory names provided."echo"$USAGE"# то выводится текст подсказкиexit 1 # и возвращается статус ошибки.
fi
while(("$#")); do# цикл, в котором происходит сдвиг аргументовwhile IFS=read -r -d $'\0' file; doecho"Removing $file..."
rm $filedone < <(find "$1" -type f -atime +365 -print0)shift
done
echo"Done."
exit 0
Давайте рассмотрим части этого скрипта, чтобы понять, что они делают.
USAGE="Usage: $0 dir1 dir2 dir3 ..."
if["$#"=="0"]; then
echo"Error: no directory names provided."echo"$USAGE"exit 1
fi
В этом операторе проверки условия if скрипт проверяет, указаны ли какие-либо аргументы. Проверка использует нотацию одиночных скобок [ ... ], что эквивалентно встроенной команде test. Специальная переменная окружения $# содержит общее количество позициональных параметров. Если её значение равно 0, то это значит, что пользователь не предоставил для скрипта ни одной директории, в этом случае будет выведен текст подсказки, и скрипт возвратит результат ошибки (exit-статус 1).
while(("$#")); do
Иначе запустится цикл while/do, в его условии применяется проверка условия с двойными круглыми скобками, чтобы проверить, соответствует ли $# значение true (это любое значение, не равне 0). Каждый раз перед запуском прокрутки тела цикла, если $# равно 0, то выражение (( "$#" )) будет вычислено как false, и произойдет выход из цикла.
Примечание: выражения (( "$#" )) и [ "$#" != "0" ] дают одинаковые результаты, и функционально взаимозаменяемые.
Рассмотрим теперь внутренний цикл while:
while IFS=read -r -d $'\0' file; do
Эта строка говорит следующее: выполнять, пока (while) имеются раздельные читаемые элементы (-d), отделенные друг от друга нулевым символом ( $'\0' ), с чтением такого элемента в переменную file, после чего выполнить команды внутри цикла (do).
Элементы предоставляются строкой в теле цикла:
done < <(find "$1" -type f -atime +365 -print0)
Эта строка говорит: найти все файлы, начинать искать в директории dir1 (позициональный параметр $1), к которой был доступ последний раз (-atime) больше чем ( + ) 365 дней тому назад. Разделить список найденных файлов нулевыми символами null (-print0).
Команда поиска find обернута в <( ... ), что использует перенаправление, обрабатывающее вывод как если бы это был файл. Содержимое "файла" перенаправляется ( < ) во внутренний цикл while. Здесь read интерпретирует все до null-символа как имя следующего файла, и назначает это имя переменной file. Затем выполняется тело внутреннего цикла, которое печатает имя удаляемого файла и удаляет его:
echo"Removing $file..."
rm $file
Внутренний цикл продолжается до тех пор, пока имена файлов не закончаться, тогда read вернет false. Произойдет выход из внутреннего цикла, где выполняется:
shift
Команда shift сдвинет позициональные параметры, так что dir2 ($2) станет теперь dir1 ($1). Старое значение $1 при сдвиге выбрасывается, и значение $# автоматически декрементируется на 1.
done
Оператор done делает возврат к началу внешнего цикла while. Если больше позициональных параметров нет, то проверка (( "$#" )) вернет false, произойдет выход из внешнего цикла, и выполнится:
echo"Done."
exit 0
Скрипт завершится успешно (exit-статус 0) с выводом сообщения "Done".