Bash имеет несколько внутренних переменных, и в этой статье мы рассмотрим переменную $$. Что означает эта переменная? Как ее можно использовать?

$$ — это внутренняя переменная Bash, которая содержит идентификатор процесса (PID) оболочки, запускающей ваш скрипт. Иногда переменную $$ путают с переменной $BASHPID, которая содержит PID текущей оболочки Bash.

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

Давайте начнем!

Значение $$ в Bash

Как уже упоминалось выше…

Переменная $$ в Bash содержит идентификатор процесса (PID) оболочки, запускающей ваш скрипт.

Чтобы продемонстрировать эту концепцию, мы создадим очень простой скрипт, который использует команду grep для вывода собственного PID. Затем скрипт также будет использовать команду echo для вывода значения переменной $$.

Таким образом, мы сможем сравнить их значения.

#!/bin/bash
   
SCRIPT_NAME=$(basename $0)
echo "The name of this script is $SCRIPT_NAME"
echo "This is the output of the ps command:"
ps -aef | grep $SCRIPT_NAME
echo "The value of \$\$ is $$" 

Вывод нашего скрипта оболочки:

$./bash_pid.sh
The name of this script is bash_pid.sh
This is the output from the ps command:
myuser 32385 31721  0 17:14 pts/0    00:00:00 /bin/bash./bash_pid.sh
myuser 32388 32385  0 17:14 pts/0    00:00:00 grep bash_pid.sh
The value of $$ is 32385

Вы можете видеть, что значение $$ совпадает с PID процесса Bash, который запускает текущий скрипт (32385).

Третий столбец вывода ps — это PPID (идентификатор родительского процесса), который в данном случае является PID текущей оболочки Bash (или процесса Bash):

$ ps -aef | grep 31721
myuser 31721 31323  0 16:31 pts/0    00:00:00 bash
myuser 32437 31721  0 17:17 pts/0    00:00:00 ps -aef
myuser 32438 31721  0 17:17 pts/0    00:00:00 grep --color=auto 31721

Вы можете увидеть то же значение, используя команду echo в текущей оболочке:

$ echo $$
31721 

Разница между $$ и BASHPID

Я просматривал руководство Bash, чтобы найти что-нибудь еще о $$.

$ man bash 

И вот что я нашел:

Bash $$ Переменная: BASHPID

Давайте посмотрим, содержит ли переменная оболочки BASHPID то же значение, что и $$:

$ echo $$
31721
$ echo $BASHPID
31721

В данном случае это так (как мы уже видели, это PID текущей оболочки).

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

echo "The value of \$BASHPID is $BASHPID"

Вывод:

$./bash_pid.sh
The name of this script is bash_pid.sh
This is the output from the ps command:
myuser 32495 31721  0 17:20 pts/0    00:00:00 /bin/bash./bash_pid.sh
myuser 32498 32495  0 17:20 pts/0    00:00:00 grep bash_pid.sh
The value of $$ is 32495
The value of $BASHPID is 32495

$BASHPID также возвращает PID оболочки Bash, используемой для выполнения текущего скрипта.

Итак, подведем итоги: значение переменных $$ и BASHPID равно:

  • PID текущей оболочки, если посмотреть на его значение непосредственно в оболочке.
  • PID оболочки, используемой для запуска текущего скрипта при выводе его значения внутри скрипта.

Значение $$ и BASHPID в подоболочках

Теперь сравним значение $$ и BASHPID, если вы используете подоболочки.

#!/bin/bash
   
echo "\$\$ outside the subshell: $$"
echo "\$BASHPID outside the subshell: $BASHPID" 

(
echo "\$\$ inside the subshell: $$"
echo "\$BASHPID inside the subshell: $BASHPID"
) 

Вот вывод скрипта:

$./subshell.sh 
$$ outside the subshell: 3145
$BASHPID outside the subshell: 3145
$$ inside the subshell: 3145
$BASHPID inside the subshell: 3146 

Таким образом, при использовании подоболочки значение $$ отличается от BASHPID.

Давайте воспользуемся командой ps, чтобы понять, как PID, возвращаемые $$ и BASHPID в скрипте, соотносятся с процессами, запущенными в нашей системе.

Сценарий становится таким:

#!/bin/bash

echo "\$\$ outside the subshell: $$"
echo "\$BASHPID outside the subshell: $BASHPID"
ps -aef | grep $$

(
echo "\$\$ inside the subshell: $$"
echo "\$BASHPID inside the subshell: $BASHPID"
ps -aef | grep $$
ps -aef | grep $BASHPID
)

В выводе вы можете видеть, что при выполнении в подоболочке $$ возвращает родительский PID подоболочки.

$./subshell.sh
$$ outside the subshell: 32586
$BASHPID outside the subshell: 32586
myuser 32586 31721  0 17:29 pts/0    00:00:00 /bin/bash./subshell.sh
myuser 32587 32586  0 17:29 pts/0    00:00:00 ps -aef
myuser 32588 32586  0 17:29 pts/0    00:00:00 grep 32586

$$ inside the subshell: 32586
$BASHPID inside the subshell: 32589
myuser 32586 31721  0 17:29 pts/0    00:00:00 /bin/bash./subshell.sh
myuser 32589 32586  0 17:29 pts/0    00:00:00 /bin/bash./subshell.sh
myuser 32591 32589  0 17:29 pts/0    00:00:00 grep 32586
myuser 32593 32589  0 17:29 pts/0    00:00:00 grep 32593

Команда Bash и переменная $$

Я знаю, это может быть сложная тема для понимания…

По этой причине я хочу рассмотреть как можно больше примеров, чтобы дать вам ясность относительно переменной $$ в Bash.

Взгляните на команды ниже:

$ echo $$
10267
$ bash -c 'echo $$'
10363 

В чем разница между этими двумя командами?

Чтобы понять это, мы также можем запустить ps как часть каждой команды:

$ echo $$; ps -aef | grep $$
31721
myuser 31721 31323  0 16:31 pts/0    00:00:00 bash
myuser 32726 31721  0 17:38 pts/0    00:00:00 ps -aef
myuser 32727 31721  0 17:38 pts/0    00:00:00 grep --color=auto 31721

$ bash -c 'echo $$; ps -aef | grep $$'
32731
myuser 32731 31721  0 17:39 pts/0    00:00:00 bash -c echo $$; ps -aef | grep $$
myuser 32732 32731  0 17:39 pts/0    00:00:00 ps -aef
myuser 32733 32731  0 17:39 pts/0    00:00:00 grep 32731

Вот несколько вещей, которые мы можем наблюдать:

  • Мы использовали точку с запятой (;) для последовательного выполнения двух команд Linux (echo и ps).
  • Значение $$ в текущей оболочке возвращает PID оболочки Bash (как мы видели ранее).
  • Команда bash -c выполняет команду в кавычках в новой оболочке. Родитель новой оболочки — наша начальная оболочка.
  • Во второй команде переменная $$ содержит PID новой оболочки, выполняющей команды.

$$ против Mktemp для временных каталогов

Иногда переменная $$ используется для генерации временных имен файлов в Linux.

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

Вы можете использовать переменную $$ для генерации имени файла для отчета о резервном копировании, учитывая, что $$ содержит PID скрипта при каждом его выполнении.

#!/bin/bash

FILENAME=daily_backup.$$
touch $FILENAME

После трехкратного запуска скрипта мы увидим в текущем каталоге следующие файлы:

$./backup.sh
$./backup.sh
$./backup.sh
$ ls
backup.sh  daily_backup.855  daily_backup.857  daily_backup.859

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

В качестве альтернативы Linux предоставляет команду mktemp.

$ mktemp
/tmp/tmp.elqilKRddX

Как вы можете видеть, он генерирует случайный временный файл в каталоге /tmp.

Если вы хотите создать временный файл в текущем каталоге с помощью mktemp, вы можете сделать это, передав флаг tmpdir:

$ mktemp --tmpdir=.
./tmp.V5intPTQHd

Завершение скрипта Bash с помощью $$

Я хочу завершить этот урок интересным экспериментом…

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

Мы знаем, что переменная $$ содержит PID оболочки, выполняющей наш скрипт.

Итак, что произойдет, если мы уничтожим PID, возвращаемый $$ внутри скрипта?

Давайте узнаем!

#!/bin/bash

echo "The PID of this script is $$"
echo "Killing this script using \$\$…"
kill $$
echo "This should never be executed"

Мы передаем PID текущего скрипта команде kill, чтобы завершить его.

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

$./self_kill.sh
The PID of this script is 17285
Killing this script using $$…
Terminated: 15

Оболочка отправляет сообщение «Terminated» на стандартный вывод. Это сообщение зависит от того, как мы завершаем процесс.

Какой статус выхода возвращает этот скрипт после завершения?

$ echo $?
143

Код выхода Bash 143 возникает из-за того, что скрипт завершается фатальным сигналом, и в этих случаях Bash возвращает код выхода, равный 128+N.

В данном случае N равно 15, что, если посмотреть на вывод kill -l, представляет SIGTERM.

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGEMT       8) SIGFPE
 9) SIGKILL     10) SIGBUS      11) SIGSEGV     12) SIGSYS
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGURG
17) SIGSTOP     18) SIGTSTP     19) SIGCONT     20) SIGCHLD
21) SIGTTIN     22) SIGTTOU     23) SIGIO       24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGINFO     30) SIGUSR1     31) SIGUSR2

Если мы используем kill -9 в нашем скрипте, мы увидим немного другой вывод:

$./self_kill.sh
The PID of this script is 18102
Killing this script using $$…
Killed: 9
$ echo $?
137

В этом случае статус выхода $? равен 128+9 = 137 (SIGKILL).

Заключение

Надеюсь, вы нашли в этом уроке то, что искали, и он дал вам достаточно ясности для работы с переменной $$ в Bash.

А как вы планируете использовать $$ в своих сценариях?

Written by Иван Васильков

Системный администратор и DevOps с опытом 10+ лет.