Чем перенаправление файлов bash к стандартным отличается от оболочки (`sh`) в Linux?

9

Я написал сценарий, который переключает пользователей во время работы, и выполнил его, используя перенаправление файлов к стандартному в. Так user-switch.shчто ...

#!/bin/bash

whoami
sudo su -l root
whoami

И запуск с этим bashдает мне поведение, которое я ожидаю

$ bash < user-switch.sh
vagrant
root

Однако, если я запускаю скрипт с sh, я получаю другой вывод

$ sh < user-switch.sh 
vagrant
vagrant

Почему bash < user-switch.shдает другой результат, чем sh < user-switch.sh?

Ноты:

  • происходит на двух разных коробках под управлением Debian Jessie
popedotninja
источник
1
Не ответ, но относительно sudo su: unix.stackexchange.com/questions/218169/…
Кусалананда
1
такое / bin / sh = dash в Debian? Я не могу воспроизвести RHEL, где / bin / sh = bash
Джефф Шаллер
1
/bin/shна (моя копия) Debian - это Dash, да. Я даже не знал, что это было до сих пор. Я застрял с Баш до сих пор.
popedotninja
1
Баш поведение не гарантируют работу любой документально семантике либо.
Чарльз Даффи
Кто-то сказал мне сегодня, что скрипт, который я запускал, нарушил семантику того, как bash предназначен для использования. Было несколько отличных ответов на этот вопрос, но стоит отметить, что, вероятно, не следует переключать пользователей в середине сценария. Первоначально я попробовал user-switch.shв работе Дженкинс, и это скрипт выполнялся. Запуск одного и того же сценария за пределами Jenkins дал разные результаты, и я прибегнул к перенаправлению файлов, чтобы получить поведение, которое я видел в Jenkins.
popedotninja

Ответы:

12

Аналогичный скрипт sudo, но без схожих результатов:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

С bash, остальная часть сценария идет как ввод sed, с dash, оболочка интерпретирует его.

Работает straceна них: dashчитает блок скрипта (здесь восемь кБ, более чем достаточно для хранения всего скрипта), а затем порождает sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Это означает, что дескриптор файла находится в конце файла и sedне будет видеть никаких входных данных. Оставшаяся часть буферизуется внутри dash. (Если длина скрипта превышает размер блока 8 кБ, оставшаяся часть будет прочитана sed.)

Bash, с другой стороны, ищет конец последней команды:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Если вход поступает из трубы, как здесь:

$ cat script.sh | bash

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

Похожая ситуация такая:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

Подоболочка должна быть уверена, что readчитает только первую новую headстроку , чтобы видеть следующую строку. (Это dashтоже работает .)


Другими словами, Bash идет на дополнительные длины, чтобы поддерживать чтение одного и того же источника как для самого скрипта, так и для команд, выполняемых из него. dashне делает. Пакет zshи ksh93в Debian идут вместе с Bash.

ilkkachu
источник
1
Честно говоря, я немного удивлен, что работает.
ilkkachu
спасибо за отличный ответ! Я выполню обе команды, используя straceпозже, и сравню результаты. Я не должен был использовать этот подход.
popedotninja
2
Да, моя реакция на чтение вопроса была неожиданной, что он действительно работает с bash - я отложил его как нечто, что определенно не сработало бы. Я думаю, я был только наполовину прав :)
Хоббс
12

Оболочка читает скрипт из стандартного ввода. Внутри скрипта вы запускаете команду, которая также хочет прочитать стандартный ввод. Какой вход будет идти куда? Вы не можете сказать достоверно .

Способ работы оболочек заключается в том, что они читают кусок исходного кода, анализируют его, и, если они находят завершенную команду, запускают команду, а затем переходят к оставшейся части фрагмента и оставшейся части файла. Если чанк не содержит завершенной команды (с завершающим символом в конце - я думаю, что все оболочки читаются до конца строки), оболочка читает другой чанк и так далее.

Если команда в сценарии пытается прочитать из того же дескриптора файла, из которого оболочка читает сценарий, то команда найдет все, что следует после последнего фрагмента, который она прочитала. Это местоположение непредсказуемо: оно зависит от того, какой размер блока выбрана оболочкой, и это может зависеть не только от оболочки и ее версии, но и от конфигурации компьютера, доступной памяти и т. Д.

Bash ищет конец исходного кода команды в сценарии перед выполнением команды. Это не то, на что вы можете рассчитывать, не только потому, что другие оболочки этого не делают, но и потому, что это работает только тогда, когда оболочка читает из обычного файла. Если оболочка читает из канала (например ssh remote-host.example.com <local-script-file.sh), прочитанные данные считываются и не могут быть прочитаны.

Если вы хотите передать входные данные команде в сценарии, вы должны сделать это явно, обычно с документом здесь . (Здесь документ обычно наиболее удобен для многострочного ввода, но подойдет любой метод.) Написанный вами код работает только в нескольких оболочках, только если сценарий передается в качестве ввода в оболочку из обычного файла; если вы ожидали, что секунда whoamiбудет передана в качестве входных данных sudo …, подумайте еще раз, имея в виду, что большую часть времени сценарий не передается на стандартный ввод оболочки.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Обратите внимание, что это десятилетие вы можете использовать sudo -i root. Бег sudo suэто взлом из прошлого.

Жиль "ТАК - прекрати быть злым"
источник