воскресенье, 24 января 2010 г.

Укрощение переменных в bash

Вчера друг задал вопрос:

есть скрипт run.sh:

#!/bin/sh
clear
echo "$# parametrs"
my_inc=$@; #### пробовал my_inc='$@'
echo $my_inc;
include '$my_inc';
. $my_inc
вызывая его c параметром my.sh:
$./run.sh my.sh
я хочу чтобы скрипт run.sh запускал файл, название которого передается как параметр
(последние две строки моего скрипта не делают этого)
вопросы :
1)почему это не проиходит?
2)можно ли как то запустить в консоли два скрипта в одной строке, так чтобы когда запустится второй скрипт, переменные с первого скрипта не уничтожились в памяти
пробовал :
$./script1.sh && ./script2.sh
$./script1.sh; ./script2.sh
не пашет, когда запускаетя второй скрипт, то уже первого скрипта переменные он не знает
-----------------

Думал, отвечу сейчас минут за пять, да и хорошо. А получилось как-то чуть по больше

Вопрос первый

  В первом случае вместо апострофов, стоит использовать кавычки. Стринг в апострофах не вычисляется, а представляется просто набором символов:

[ant@CentOS 2]$ cat c;./c
#!/bin/bash
a="slon"
b='$a'
c=$a
d="$a"
echo a=$a
echo b=$b
echo c=$c
echo d=$d
#-EOF-

a=slon
b=$a
c=slon
d=slon
[ant@CentOS 2]$
т.о '$b' эквивалентно "\$b" в то время как "$b" эквивалентно содержимому переменной b. Достаточно заменить в данной ситуации апострофы на кавычки. Если бы не необходимость в передаче параметров то можно было бы, вообще, все это без кавык использовать. Нужны они из-за пробелов обусловленных наличием нескольких передаваемых параметров командной строки. Если нужно присвоить переменной выражение с пробелами, то приходится либо заключать его в кавычки/апострофы либо квотировать каждый пробел.

var="what's this? it's a $cat ;)"
altvar=what\'s\ this?\ it\'s\ a\ $cat\ \;\)
да и кстати ключевого слова include в bash нету, в место него используется точка

Вопрос второй

  Вообще тут надо помнить, что каждый процесс имеет своё окружение. Так вот переменные этого окружения наследуются в порожденный процесс. Шелл использует понятие переменной как некий внутренний механизм, который с этим окружением никак не связан. Это всего лишь конструкция языка. В bash для того чтобы создать переменную окружения с таким же названием как и шелловская используется export. Т.о.
если
[ant@CentOS 2]$ a=foo
[ant@CentOS 2]$ b=bar
[ant@CentOS 2]$ export b
[ant@CentOS 2]$ cat >abc;chmod 755 abc
!#/bin/sh
echo a=$a
echo b=$b
^D
то
[ant@CentOS 2]$ ./abc
a=
b=bar
[ant@CentOS 2]$
Итак, ведущий шелл породил процесс со скриптом abc. И abc унаследовал лишь ту переменную, которая в родительском процессе была "экспортирована" в окружение. Т.е. если говорить более корректно - создана переменная окружения с таким же именем как внутренняя и ей присвоено значение внутренней шелловской переменной.

  Обратное действие, т.е создание из переменной окружения, внутренней переменной, по умолчанию шелл выполняет автоматически при старте. Чем-то напоминает поведение php при magic_vars=1 и получении внешних параметров ;)

  В примере из второго вопроса из текущего шелла сначала порождается один процесс со скриптом, а потом другой. Нет никакой возможности из порожденного процесса поменять окружение родительского (для обмена данными между двумя "живыми" процессами используются механизмы межпроцессного взаимодействия: каналы, сокеты, сообщения и т.д, но это уже выходит за рамки обычного скриптования).

  Вообще говоря, поведение export требует более внимательного рассмотрения, а лучше всего почитать man :) Дело в том, что эта команда не просто создает переменную окружения. Она помечает внутреннюю переменную, для автоматического её дублирования в окружении. И как только переменная помечается, она тут же автоматически дублируется в окружение. При изменении помеченной переменной, изменится и соответствующая переменная окружения. Иначе пришлось бы каждый раз после изменения, выполнять
export var
Т.о. создается иллюзия что переменная "приобрела статус" переменной окружения. Более того!!! Если в текущем шелле есть такая переменная, то само собой она будет наследоваться в порожденный шелл(да и в любой другой порожденный процесс), но стоит обратить внимание, что в порожденном шелле она УЖЕ будет помечена, поскольку это довольно логично, помечать все что было созданно из перемнных окружения при запуске. Можно провести такой трюк. Например, есть скрипт который вызывает другой скрипт. Известно, что первый скрипт использует переменную slon, а второй скрипт делает, просто, echo $slon и ничего больше. Можно в ведущем шелле проинициализировать экспортируемую переменную slon. Вот что из этого получится.

[ant@CentOS 2]$ cat >script1&&cat >script2&&chmod 755 script[12]
#!/bin/sh
slon="some value"
./script2
#--EOF script1--
^D
#!/bin/sh
echo script2: slon=$slon
#--EOF script2--
^D
[ant@CentOS 2]$ export slon=
[ant@CentOS 2]$ ./script1
script2: slon=some value
[ant@CentOS 2]$
теперь снимем пометку с переменной слон (параметр -n) какбы unexport
[ant@CentOS 2]$ export -n slon
[ant@CentOS 2]$ ./script1
script2: slon=
[ant@CentOS 2]$
Что и требовалось доказать. Когда переменная была помечена экспортной, то в порожденном процессе script1, при изменении внутренней переменной slon, также происходило и изменение переменной окружения slon. Поэтому когда script1 породил script2, и второй скрипт сделал своё echo, то мы увидели значение, которое было присвоено в script1. Т.е. script2 автоматически унаследовал переменную из script1, хотя и в script1 не далалось никакого export. И КОНЕЧНО ЖЕ созданная нами в ведущем шелле пустая переменная slon НЕ ИЗМЕНЯЛАСЬ в процессе работы порожденных скриптов - см. предыдущий абзац. Благодаря этому механизму и работают такие штуки как PATH SHELL PS1 и прочие.

  На заметку. Условие передачи окружения в порождаемый процесс не абсолютное - можно порождать процессы с окружением без переменных. Bash здесь не исключение, у него есть параметры с помощью которых можно изменить это условие. Так же с помощью специальных параметров команды set можно изменить это условие у текущего (выполняющего данный скрипт или ожидающего ввода команд) интерпретатора.

Итак, здесь возможны варианты:

1. В первом скрипте с помощью export вынести нужные переменные в его окружение, и породить второй процесс (из первого)

--script1--
#!/bin/sh
a=foo; b=bar; export a b
path=<path>
$path/script2

--script2--
#!/bin/sh
echo -e a=$a \\nb=$b
2. В первом скрипте используя точку присобачить в его тело содержимое второго скрипта, тогда ничего экспортировать не надо, второй скрипт просто будет выполнятся как продолжение первого, ну это похоже на пример из первого вопроса.

--script1--
#!/bin/sh
a=foo; b=bar;
path=<path>
. $path/script2
3. Перед завершением первого скрипта, выплевывать в какой-нить temp переменные, а вторым скриптом их цеплять и удалять temp

--script1--
#!/bin/sh

tmp=/tmp/sometemp
>$tmp

var1="Fee fie foe fum"
var2="I smell the blood of an english man"
var3="Be he live or be he dead"
var4="I'll grind his bones to make my bread"

dumpvars="var1 var2 var3 var4"
for vname in $dumpvars;do
echo $vname=\"${!vname}\" >>$tmp
done

--script2--
#!/bin/sh
tmp=/tmp/sometemp
. $tmp
echo var1=$var1
echo var2=$var2
echo var3=$var3
echo var4=$var4
rm -f $tmp
[ant@CentOS 2]$ ./script1 && ./script2
      или
[ant@CentOS 2]$ ./script1; ./script2
разница лишь в том что в первом случае script2 будет запускаться только при успешном завершении script1. Этот вариант, конечно, несколько надуман, но мало ли какая бывает ситуация :) Выражение ${!vname} на самом деле используется для того чтобы зная название переменной получить её значение. Вроде как "разыменование косвенной ссылки". А в for'е перечисляются названия переменных которые нужно выбросить в темп в виде
name="value"
по сути это выражение как бы реализация $$val но в шеле это не может сработать потому как он воспринимает его как $$ и 'val', а $$ возвращает id текущего процесса. В более ранних реализация шелла (<V.2.xx) пришлось бы завести дополнительную переменную и записать как-то так:
eval var=\$$vname
Конечно возможны варианты передачи данных из одного процесса в другой по конвейеру, типа script1|script2 предварительно сжав их через gzip и прогнав base64 %))), либо вместо временного файла использовать unix socket, хотя я не пробовал работать с сокетами из скриптов, либо еще чего-нибудь эдакое замыслить интересное.

5 комментариев:

  1. спасибо , получилось !!!!

    Поздравляю с первым сообщением в своем блоге! Подписался в Google Reader, буду ждать новостей с блога!!

    ОтветитьУдалить
  2. еще вариант:
    ---run.sh----
    #!/bin/bash
    var=1
    . $1

    ---my.sh---
    #!/bin/bash
    echo $var

    $./run.sh my.sh
    1

    ОтветитьУдалить
  3. Простейший случай существенной разницы между этими вариантами -- использование команды exit в моем скрипте. В случае если скрипт вызывается bash script2 после неё произойдёт возврат в первый скрипт, если .script2 -- выход из первого скрипта.

    ОтветитьУдалить
  4. По поводу первого комента. Дык я и описал этот случай у себя в варианте N2
    --script1--
    #!/bin/sh
    a=foo; b=bar;
    path=
    . $path/script2
    да и ты с него как раз и начинал - твой первый вопрос там где апострофы вместо кавык были. Но просто использовать $* или $@ более удобно, это позволит прокинуть параметры в последний вызываемый скрипт. Просто в своем описании я делал упор именно на механизм передачи данных от скрипта к скрипту, не вдаваясь в частности.

    насчет второго комента не совсем понял, что ты хотел сказать. Если ты из скрипта вызываешь другой скрипт, то первый скрипт порождает процесс bash script2 и переходит в режим ожидания завершения этого потомка. Когда потомок завершается, первый скрипт просыпается и продолжает свое выполнение. А чего уж он там достигает, exit'а или конца файла, какая разница? В случае точки (source) порождения нового процесса не происходит, проосто содержимое второго скрипта добавляется в то место где эта точка стоит, и выполняется, все в рамках того же процесса

    ОтветитьУдалить
  5. Кстати думаю тут будет втему

    http://ftp.sumylug.osdn.org.ua/pub/docs/mirrors/gazette.linux.ru.net/rus/articles/abs-guide/c3242.html#APPREF

    ОтветитьУдалить