ПРИМІТКА: @ jw013 в наступних коментарях робить таке непідтримуване заперечення:
Основна причина полягає в тому, що самовиправляючий код, як правило, вважається поганою практикою. Ще в часи крихітних програм складання це був розумний спосіб зменшити умовні гілки та підвищити продуктивність, але в наш час ризики для безпеки переважують переваги. Ваш підхід не працював, якби користувач, який керував сценарієм, не мав привілеїв для запису сценарію.
Я відповів на його заперечення щодо безпеки, зазначивши, що будь-які спеціальні дозволи потрібні лише один раз на рік встановлення / оновлення дії для того , щоб встановити / оновити в самоустановлювальний сценарій - який я особисто називаю досить безпечним. Я також вказав йому на man shпосилання на досягнення подібних цілей подібними засобами. На той час я не намагався зазначити, що які б вади безпеки чи взагалі не були відредаговані практики, які можуть бути або не можуть бути представлені у моїй відповіді, вони, скоріш за все, укорінені в самому питанні, ніж у моїй відповіді на нього:
Як я можу налаштувати шебанг, щоб запуск сценарію як /path/to/script.sh завжди використовував Zsh, доступний у PATH?
Не задоволений, @ jw013 продовжував заперечувати, продовжуючи свій ще непідтримуваний аргумент принаймні парою помилкових тверджень:
Ви використовуєте один файл, а не два файли. У
пакеті [ man shпосилання] один файл змінює інший файл. У вас є файл, що модифікує себе. Існує чітка різниця між цими двома випадками. Файл, який приймає вхід і видає вихід, добре. Виконаний файл, який змінюється під час роботи, як правило, погана ідея. Приклад, на який ви вказали, цього не робить.
На першому місці:
ЄДИНИЙ ВИКОНАВЧИЙ КОД У БУДЬ-ЯКІЙ ВИКОНАВЧИЙ СКРИПТ СКЛАДУЄ #!САМО
(хоча навіть #!є офіційно не визначено )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
Сценарій оболонки це просто текстовий файл - для того , щоб мати якийсь - або ефект на всіх це повинно бути прочитати інший виконуваний файл, його інструкція потім інтерпретована цим іншим виконуваним файлом, поки , нарешті , інший виконуваний файл , то виконує свою інтерпретацію з скрипт оболонки. Це неможливо виконання файлу сценарію оболонки задіяти менше двох файлів. Можливий виняток у zshвласного компілятора, але з цим у мене мало досвіду, і він тут жодним чином не представлений.
Хешбанг сценарію оболонки повинен вказувати на призначеного інтерпретатора або відкидати його як нерелевантне.
Оболонка має два основні режими розбору та інтерпретації її вводу: або її поточний вхід визначає а, <<here_documentабо визначає а { ( command |&&|| list ) ; } &- іншими словами, оболонка або інтерпретує маркер як роздільник для команди, яку він повинен виконати, як тільки прочитав його в інструкції або як створити файл і відобразити його в дескрипторі файлів для іншої команди. Це воно.
При інтерпретації команд для виконання оболонки розмежовуються лексеми на наборі зарезервованих слів.Коли оболонка виявляє отвір маркерів він повинен продовжувати читати в списку команд , поки перелік не буде або розмежовуються закриттями маркерів , такі як символ нової рядок - коли це може бути застосовано - або закриття фішки як })для ({перед виконанням.
Оболонка розрізняє просту команду і складну команду. Команда з'єднання являє собою набір команд , які повинні бути лічені в перед виконанням, але оболонка не виконує $expansionні на одному з його складові команд простих до тих пір, поки по окремо виконує кожен з них.
Отже, у наступному прикладі ;semicolon зарезервовані слова розмежовують окремі прості команди, тоді як \newlineсимвол, який не уникнув, розмежовує між двома складеними командами:
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
Це спрощення керівних принципів. Це набагато складніше, якщо врахувати оболонки, вкладені в оболонки, поточне середовище тощо, але для моїх цілей цього тут достатньо.
І якщо говорити про вбудованих модулях та командних списках,function() { declaration ; } є лише засобом призначення з'єднання команди до простої команді. Оболонка не повинна виконувати жодну$expansions заяв декларації - включати <<redirections>-, а повинна зберігати визначення як єдиний, буквальний рядок і виконувати його як спеціальну вбудовану оболонку, коли її вимагають.
Таким чином, функція оболонки, оголошена у виконавчому скрипті оболонки, зберігається в пам'яті оболонки інтерпретації у її буквальному рядковому вигляді - нерозширюється, щоб включити додані тут документи як вхідні дані - і виконується незалежно від його вихідного файлу кожного разу, коли вона викликається як оболонка, протягом тих пір, поки триває поточне середовище оболонки.
Оператори перенаправлення <<і <<-обидва дозволяють перенаправляти рядки, що містяться у вхідному файлі оболонки, відомому як тут-документ, на вхід команди.
Цей документ розглядається як одне слово, яке починається після наступного \newlineі продовжується, поки не з’явиться рядок, що містить лише роздільники та a \newline, без [:blank:]проміжків s. Потім починається наступний документ тут , якщо такий є. Формат такий:
[n]<<word
here-document
delimiter
... де необов'язково nпредставляє номер дескриптора файлу. Якщо число опущено, цей документ стосується стандартного введення (дескриптор файлу 0).
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
Розумієш? Для кожної оболонки над оболонкою створюється файл і відображається його в дескрипторі файлу. У zsh, (ba)shоболонці створюється звичайний файл у /tmp, скидається висновок, відображається його в дескрипторі, а потім видаляється /tmpфайл, щоб копія дескриптору ядра була все, що залишилося. dashуникає всіх цих дурниць і просто переводить свою обробку виводу в анонімний |pipeфайл, спрямований на <<ціль переадресації .
Це робить dash:
cmd <<HEREDOC
$(cmd)
HEREDOC
функціонально еквівалентний bash's:
cmd <(cmd)
в той час як dashреалізація є принаймні POSIXly портативною.
ЯК РОБИТИ РІЗНІ ФАЙЛИ
Тож у відповіді нижче, коли я це роблю:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
Буває таке:
Я спочатку catвміст будь-який файл оболонки , створений для FILEв./file , зробити його виконуваним, а потім виконати його.
Ядро інтерпретує #!і виклики /usr/bin/shз <read дескриптором файлу , призначеним ./file.
shвідображає рядок у пам'ять, що складається з складної команди, що починається на _fn()і закінчується в SCRIPT.
Коли _fnвикликається, shспочатку слід інтерпретувати, а потім відобразити в дескрипторі файл, визначений в ньому, <<SCRIPT...SCRIPT перш ніж викликати _fnяк спеціальну вбудовану утиліту, оскільки SCRIPTце_fn «s<input.
Виведення рядка з допомогою printfі commandвиписаний на _fn«s стандартний вихід >&1 - який перенаправляється на поточному оболочечном ARGV0- або$0 .
catоб'єднує <&0 стандартний вхідний файл-дескриптор - SCRIPT- над аргументом >усіченої поточної оболонки ARGV0, або $0.
Виконуючи свою вже прочитану в поточній складовій команді , sh execs виконуваний - і знову переписаний - $0аргумент.
З часу ./fileвиклику до тих пір, поки його вміщені інструкції не вказують, що він повинен бути execd знову, shчитає його в одній складеній команді за один раз, коли він виконує їх, а ./fileсам не робить нічого, крім того, щоб радісно прийняти його новий вміст. Файли, які насправді працюють, є/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
ДЯКУЄМО, ПІСЛЯ ВСІХ
Отже, коли @ jw013 вказує, що:
Файл, який приймає введення та виробляє вихід, добре ...
... серед своєї помилкової критики щодо цієї відповіді він насправді мимоволі потурає єдиному методу, що використовується тут, який, в основному, працює просто:
cat <new_file >old_file
ВІДПОВІДЬ
Усі відповіді тут хороші, але жодна з них не є повністю коректною. Начебто всі стверджують, що ви не можете динамічно та постійно прокладати шлях#!bang . Ось демонстрація встановлення незалежного шебангу:
DEMO
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
ВИХІД
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
Розумієш? Ми просто змушуємо сценарій перезаписати себе. І це відбувається лише один раз після gitсинхронізації. З цього моменту він отримав правильний шлях у рядку #!
Зараз майже все це є просто пух. Для цього безпечно вам потрібно:
Функція, визначена вгорі і викликана внизу, яка виконує написання. Таким чином ми зберігаємо все необхідне в пам'яті і забезпечуємо зачитування всього файлу, перш ніж ми починаємо писати його.
Якийсь спосіб визначення, яким повинен бути шлях. command -vдосить добре для цього.
Гередоки справді допомагають, оскільки вони фактичні файли. Тим часом вони зберігатимуть ваш сценарій. Ви також можете використовувати рядки, але ...
Ви повинні переконатися, що оболонка читає команду, яка перезаписує ваш скрипт у тому ж списку команд, що і той, що виконує його.
Подивіться:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
Зауважте, що я перемістив execкоманду лише на один рядок. Зараз:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
Я не отримую другу половину виводу, оскільки сценарій не може прочитати в наступній команді. Але тому, що єдиною командою, яка відсутня, була остання:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
Сценарій вийшов так, як і мав бути - здебільшого тому, що це все було в гередоці - але якщо ви не плануєте його правильно, ви можете усікати свій файл файлів, що саме сталося зі мною вище.
env, що немає в / bin та / usr / bin? Спробуйтеwhich -a envпідтвердити.