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

13

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

#!/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, и у пользователя x открыто 3 терминала. Если я выполню этот код в 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 всем этим исполняемым файлам.

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

Рамана Редди
источник
разница в том, какая оболочка выполняется. sh not bash
J0h
2
Два последних исполнения также отличаются, потому что вы выполняете в одном и том же контексте. Больше здесь
Zaka Elab
Я пытаюсь подсчитать количество экземпляров bash (здесь оно равно количеству терминалов), открытых другим пользователем (не тем пользователем, в котором мы вошли), и не могли бы вы объяснить, почему в каждом случае приходилось разное число
Рамана Редди
@RamanaReddy другой пользователь, возможно, запустил скрипт или открыл новую вкладку. Кто знает?
Муру

Ответы:

21

Единственное основное различие заключается в поиске и выполнении сценария. 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

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

    $ 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

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

  2. Прямой запуск с явной (bash) оболочкой

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

    Здесь, поскольку вы работаете с bash foo.sh, результат psбудет показан bash foo.shи будет подсчитан. Итак, здесь у нас есть родительский процесс, bashзапуск сценария и клонированная оболочка (запуск 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.

terdon
источник
Когда вы говорите, что подстановка команд запускает копию родительской оболочки, чем эта копия отличается от вложенной оболочки, например, когда вы запускаете скрипт с помощью ./foo.sh?
Дидье А.
И когда вы запускаете pgrep без подстановки команд, я предполагаю, что он запускается из той же оболочки, в которой работает скрипт? Так похоже на поиск?
Дидье А.
@didibus Я не уверен, что ты имеешь в виду. Подстановка команд выполняется в подоболочке; ./foo.shзапускается в новой оболочке, которая не является копией родительского. Например, если вы установите foo="bar"в своем терминале, а затем запустите скрипт, который выполняется echo $foo, вы получите пустую строку, поскольку оболочка скрипта не унаследует значение переменной. pgrepэто отдельный двоичный файл, и да, он запускается сценарием, который вы запускаете.
тердон
В основном мне нужно разъяснение: «обратите внимание на отсутствие замены команды». Почему при запуске двоичного файла pgrep из сценария не добавляется дополнительная оболочка, а при запуске двоичного файла ps с подстановкой команд? Во-вторых, мне нужно уточнить «копию родительской оболочки», это как суб-оболочка, где переменные оболочки родительского объекта копируются в дочерний объект? Почему подстановка команд делает это?
Дидье А.