В чем разница между выполнением сценариев оболочки с использованием «source file.sh», «./file.sh», «sh file.sh», ». ./file.sh»?

Посмотрите на код:

#!/bin/bash read -p "Eneter 1 for UID and 2 for LOGNAME" choice if [ $choice -eq 1 ] then read -p "Enter UID: " uid logname=`cat /etc/passwd | grep $uid | cut -f1 -d:` else read -p "Enter Logname: " logname fi not=`ps -au$logname | grep -c bash` echo "The number of terminals opened by $logname are $not" 

Этот код используется для определения количества терминалов, открытых пользователем на одном ПК. Теперь есть два пользователя, которые вошли в систему, скажем, x и y. В настоящее время я зарегистрирован как y, и есть 3 терминала, открытых в пользователе x. Если я выполняю этот код в y различными способами, как указано выше, результаты:

 $ ./file.sh The number of terminals opened by x are 3 $ bash file.sh The number of terminals opened by x are 5 $ sh file.sh The number of terminals opened by x are 3 $ source file.sh The number of terminals opened by x are 4 $ . ./file.sh The number of terminals opened by x are 4 

Примечание. Я передал 1 и uid 1000 всем этим исполняемым файлам.

Теперь вы можете объяснить различия между ними?

Единственное существенное различие заключается в поиске и выполнении сценария. source foo.sh будет его источником, и все остальные примеры, которые вы показываете, выполняются. Более детально:

  1. ./file.sh

    Это выполнит скрипт под названием file.sh который находится в текущем каталоге ( ./ ). Обычно, когда вы запускаете command , оболочка будет просматривать каталоги в вашей $PATH для исполняемого файла, называемого command . Если вы даете полный путь, например /usr/bin/command или ./command , то $PATH игнорируется и этот конкретный файл выполняется.

  2. ../file.sh

    Это в основном то же самое, что и ./file.sh за исключением того, что вместо поиска в текущем каталоге file.sh он ищет в родительском каталоге ( ../ ).

  3. sh file.sh

    Это эквивалентно sh ./file.sh , как и выше, он запустит скрипт под названием file.sh в текущем каталоге. Разница в том, что вы явно используете его с оболочкой sh . В системах Ubuntu это dash а не bash . Обычно в сценариях есть строка shebang, которая дает программе их запуск. Вызов их с другой отменяет это. Например:

     $ cat foo.sh #!/bin/bash ## The above is the shebang line, it points to bash ps h -p $$ -o args='' | cut -f1 -d' ' ## This will print the name of the shell 

    Этот скрипт просто напечатает имя оболочки, используемой для ее запуска. Давайте посмотрим, что он возвращает при вызове по-разному:

     $ bash foo.sh bash $ sh foo.sh sh $ zsh foo.sh zsh 

    Таким образом, вызов вызова сценария со сценарием shell script переопределит строку shebang (если есть) и запустит скрипт с любой оболочкой, которую вы ему скажете.

  4. source file.sh или . file.sh . file.sh

    Это называется, как ни странно, источником сценария. Источник ключевого слова – это псевдоним для встроенной оболочки . команда. Это способ выполнения скрипта в текущей оболочке. Обычно, когда скрипт выполняется, он запускается в своей собственной оболочке, которая отличается от текущей. Проиллюстрировать:

     $ cat foo.sh #!/bin/bash foo="Script" echo "Foo (script) is $foo" 

    Теперь, если я установлю переменную foo на что-то еще в родительской оболочке, а затем запустим скрипт, скрипт напечатает другое значение foo (потому что оно также установлено внутри скрипта), но значение foo в родительской оболочке будет не измениться:

     $ foo="Parent" $ bash foo.sh Foo (script) is Script ## This is the value from the script's shell $ echo "$foo" Parent ## The value in the parent shell is unchanged 

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

     $ source ./foo.sh Foo (script) is Script ## The script's foo $ echo "$foo" Script ## Because the script was sourced, ## the value in the parent shell has changed 

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


Учитывая все это, причина, по которой вы получаете разные ответы, – это, прежде всего, то, что ваш скрипт не делает то, что вы думаете. Он подсчитывает количество раз, когда bash появляется на выходе ps . Это не количество открытых терминалов , это количество запущенных оболочек (на самом деле это даже не так, но это еще одно обсуждение). Чтобы уточнить, я немного упростил ваш скрипт:

 #!/bin/bash logname=terdon not=`ps -au$logname | grep -c bash` echo "The number of shells opened by $logname is $not" 

И запустите его по-разному, открыв только один терминал:

  1. Прямой запуск, ./foo.sh .

     $ ./foo.sh The number of shells opened by terdon is 1 

    Здесь вы используете линию shebang. Это означает, что скрипт выполняется непосредственно по всем тем, что там установлено. Это влияет на то, как скрипт отображается на выходе ps . Вместо того, чтобы быть указанным как bash foo.sh , он будет отображаться только как foo.sh что означает, что ваш grep его пропустит. На самом деле есть три экземпляра bash: родительский процесс, bash, выполняющий скрипт, и еще один, который запускает команду ps . Последнее важно, запуск команды с подстановкой команд ( `command` или $(command) ) приводит к копированию родительской оболочки, запускаемой и выполняющей команду. Здесь, однако, ни один из них не показан из-за того, как ps показывает свой вывод.

  2. Прямой запуск с явным (bash) shell

     $ bash foo.sh The number of shells opened by terdon is 3 

    Здесь, поскольку вы работаете с bash foo.sh , вывод ps покажет bash foo.sh и будет засчитан. Итак, здесь у нас есть родительский процесс, в котором запущен скрипт и клонированная оболочка (работает ps ), потому что теперь ps покажет каждый из них, потому что ваша команда будет включать слово bash .

  3. Прямой запуск с другой оболочкой ( sh )

     $ sh foo.sh The number of shells opened by terdon is 1 

    Это отличается от того, что вы запускаете скрипт с sh а не bash . Поэтому единственным экземпляром bash является родительская оболочка, в которой вы запустили свой скрипт. Все остальные оболочки, упомянутые выше, запускаются вместо sh .

  4. Sourcing (или . Или source , то же самое)

     $ . ./foo.sh The number of shells opened by terdon is 2 

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


В заключение, правильный способ подсчета запущенных процессов – это не разбирать ps а использовать pgrep . Все эти проблемы можно было бы избежать, если бы вы просто запускали

 pgrep -cu terdon bash 

Итак, рабочая версия вашего скрипта, который всегда печатает правильное число (обратите внимание на отсутствие подстановки команд):

 #!/usr/bin/env bash user="terdon" printf "Open shells:" pgrep -cu "$user" bash 

Это вернет 1 при получении и 2 (потому что для запуска скрипта будет запущен новый bash) для всех остальных способов запуска. Он все равно вернет 1 при запуске с sh поскольку дочерний процесс не является bash .