Я насчитал 10 способов запуска. И три разных 'echo' на одной убунте. И узнал некоторые трюки. Пока искал, как перекинуть изменённую переменную из дочернего процесса в родительский.
- Встраивание сценария в текущий процесс bash
- Запуск дочернего процесса
- Какие ещё способы запуска?
- echo
- Записать переменную в файл
- printenv
Встраивание сценария в текущий процесс bash
Когда мы вызываем скрипт из терминала — то этот скрипт становится отдельным процессом, дочерним от процесса терминала. Он наследует все переменные среды, которые были объявлены через export $VAR (а не просто $VAR — простые не будут видны.) Когда этот дочерний скрипт изменяет унаследованную переменную — эта переменная уже его лично, в родительском процессе значение переменной не меняется. Надёжный способ передать из дочернего процесса в родительский какую-то инфу — это возвратить какие-то результаты, как из функции, или послать вывод дочерней функции в пайп.
bash ввёл такую тему: «встраивание» кода, прочитанного из другого файла прямо в текущий процесс. Файл не обязан быть исполняемым, а по правилам хорошего тона — его таким и не делают.
Примеры таких команд:
. other_file
source other_file #это полный синоним точки, создан для понятности
source .venv/bin/activate #помните такое?
source .bashrc #применяем изменения, когда обновили .bashrc
source .profile #применяем изменения, когда обновили .profile
Это позволяет исполнить посторонний код, он обновляет переменные, а текущий процесс этим пользуется. Если бы посторонний код был запущен как дочерний — это не получилось бы. Если подключаемы скрипт захочет навредить — он навредит. Если в нём exit — то весь процесс закроется, не отработав до конца.
В подключаемом скрипте могут быть только переменные и функции — тогда переменные запишутся в текущее окружение, а функции будут доступны для вызова. Если и в текущем и в новом скрипте есть функции с одинаковым именем — нужно сперва текущую переназвать declare -f new_function_name=old_function_name
, а потом делать вставку source side_bashcode
.
Говорят, что ради возможности загрузить переменные из посторонних источников это и сделали. Ради того, чтоб у каждого пользователя был свой личный .profile (вызывается при логине) и .bashrc (вызывается при создании терминала). Это не синонимы. Окошко с терминалом можно закрыть, или открыть второе — .bashrc прочтётся заново. Даже без GUI — команда exec $SHELL
делает то же самое, заставляет заменить текущий процесс командной оболочки на новый процесс, возможно даже на другое приложение. Оно запустится с нуля, как будто после логина. Но без самого логина, без перезагрузки .profile
. Зато перезагрузится .bashrc.
Оригинальный, более старый и «совместимый со всеми» shell (sh), похоже, такого не умеет.
Читайте также: Как использовать коды завершения в Bash-скриптах
Запуск дочернего процесса
Это «обычный» запуск. Когда запускаем скрипт по имени, или приложение — то «родительская» (та, из которой исходит команда) прилога создаёт копию своего процесса, грузит в процесс код из запускаемого приложения, и «передаёт управление» = прекращает посылать команды, начинает с команды новой прилоги, которая описана как «точка входа». Примеры:
sh script.sh
bash script.sh
~/script.sh
Чтобы вызвать так, нужно поставить разрешение «execute». Если это не машкод, а текст — в нём указать первой строкой #!/path/to/interpreter. Если хочешь вызывать его из любого каталога — добавь его в одну из папок $PATH, или папку с ним добавь в $PATH. Вуаля, системная команда, как у взрослых!
Дочка может унаследовать права от матери. Если найдёт себе материнский процесс, работающий под именем root, и тот позволит отпочковаться с теми же правами — вау, у процесса права на всё!
Какие ещё способы запуска?
#!/bin/bash
SCRIPT_PATH="/path/to/script.sh"
# Insert
. "$SCRIPT_PATH"
# Insert
source "$SCRIPT_PATH"
# Fork
"$SCRIPT_PATH"
# Fork
bash "$SCRIPT_PATH"
# ?
eval '"$SCRIPT_PATH"'
# ?
OUTPUT=$("$SCRIPT_PATH")
echo $OUTPUT
# ?
OUTPUT=`"$SCRIPT_PATH"`
echo $OUTPUT
# обратные кавычки тоже запускают скрипт!
./script-that-consumes-argument.sh `sh script-that-produces-argument.sh`
# ?
("$SCRIPT_PATH")
# ?
(exec "$SCRIPT_PATH")
Вариант с двумя файлами:
# cat showdate.sh
#!/bin/bash
echo "Date is: `date`"
# cat mainscript.sh
#!/bin/bash
echo "You are login as: `whoami`"
echo "`/bin/sh ./showdate.sh`" # exact path for the script file
# ./mainscript.sh
You are login as: root
Date is: Thu Oct 17 02:56:36 EDT 2013
&
в конце — он позволит форкнуть процесс и тут же отключить его от терминала. Процесс работает сам по себе, а мы его не ждём, можем вводить новые команды или запускать параллельные процессы.
./myscript.sh &
echo
Внимейшн! Я узнал три варианта echo
:
- echo в sh — команда, которая не понимает опции -e (она её просто печатает как будто это целевой текст), но зато понимает, что \n (и другие эскейпы) нужно превращать в спецсимволы;
- echo в bash — команда, которая без опции -e не превращает \n и другие эскейпы, ей нужно прямо заказать опцию -e — и тогда начнёт превращать;
- echo в GNU utilites — вообще самостоятельная прилога, у неё тоже какие-то особенности.
В общем, советы в интернете противоречат друг другу, проще прочитать man echo о том варианте, который используете вы.
Записать переменную в файл
var="text to append";
destfile=/some/directory/path/filename
if [ -f "$destfile" ]
then
echo "$var" > "$destfile"
fi
Попроще:
echo "$var" > "$destfile"
printf работает как в C:
printf "%s" "$var" > "$destfile"
Эти ответы не сработают, если текст:
- начинается с -e, или с -n, или с -E
- или содержит \n
- не должен заканчиваться переводом строки, когда будет записан в файл.
Что тогда поможет?
cat <<< "$var" > "$destfile"
printenv var > "$destfile"
printenv
printenv [name] -- print out the environment [or just only name value if specified] env [-] [-i] [name=value ...] [utility [argument ...]] -- set and print environment
Я видел одно упоминание этой команды, а команда есть!