Шлях незалежних шебангів


20

У мене є сценарій, який я хочу мати можливість працювати на двох машинах. Ці дві машини отримують копії сценарію з одного сховища git. Сценарій повинен працювати з правильним інтерпретатором (наприклад zsh).

На жаль, як env і zshжити в різних місцях , в локальних і віддалених машинах:

Віддалена машина

$ which env
/bin/env

$ which zsh
/some/long/path/to/the/right/zsh

Місцева машина

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Як я можу налаштувати шебанг, щоб сценарій, як /path/to/script.shзавжди, використовував Zshдоступний в PATH?


8
Ви впевнені env, що немає в / bin та / usr / bin? Спробуйте which -a envпідтвердити.
grawity

Відповіді:


22

Це неможливо вирішити безпосередньо через shebang, оскільки shebang суто статичний. Що ви можете зробити, це мати якийсь "найменш спільний множник" (з точки зору оболонки) в шебанге і повторно виконати свій скрипт правою оболонкою, якщо цей LCM не zsh. Іншими словами: У сценарій запускається оболонка знайдено на всіх системах, тест на zsh-Тільки функції , і якщо тест виявляється хибним, є скрипт execз zsh, де тест буде успіх , і ви просто продовжувати.

Однією унікальною особливістю zsh, наприклад, є наявність $ZSH_VERSIONзмінної:

#!/bin/sh -

[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}

# zsh-specific stuff following here
echo "$ZSH_VERSION"

У цьому простому випадку сценарій спочатку виконується /bin/sh(усі системи, схожі на Unix після 80-х років, розуміють #!і мають /bin/sh, як Bourne, так і POSIX, але наш синтаксис сумісний з обома). Якщо $ZSH_VERSIONце НЕ встановлено, сценарій exec«S себе через zsh. Якщо $ZSH_VERSIONвстановлено (відповідно сценарій вже запущений zsh), тест просто пропускається. Voilà.

Це не вдається лише в тому випадку, якщо його zshвзагалі немає $PATH.

Edit: Для того, щоб переконатися, ви тільки в звичайних місцях, ви могли б використовувати що - щось на зразокexeczsh

for sh in /bin/zsh \
          /usr/bin/zsh \
          /usr/local/bin/zsh; do
    [ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done

Це може врятувати вас від випадкових випадків execу вашому, $PATHчого не zshви очікуєте.


Я підтримав це для елегантності, але, в принципі, це проблеми з безпекою / сумісністю, якщо перший zshв $PATHне той, який ви очікуєте.
Райан Рейх

Намагався вирішити це. Питання в тому, чи завжди ви можете бути впевнені, чи справді є zshбінарним файлом у стандартних місцях zsh?
Андреас Візе

Ви можете динамічно прокладати лінію! Bang. Ви також можете запитати zshсебе, де це zsh -c 'whence zsh'. Більш просто ви можете просто command -v zsh. Дивіться мою відповідь, як динамічно прокладати шлях #!bang.
mikeserv

1
Виклик zshбінарного файлу, $PATHщоб отримати шлях zshбінарного, не зовсім вирішив би проблему, яку вказував @RyanReich, чи не так? :)
Андреас Візе

Ні, якщо ви виконуєте zshсебе, ні, я думаю, що ні. Але якщо вбудувати отриману рядок у хеш-баг, а потім виконати власний сценарій, ви принаймні знаєте, що ви отримуєте. Але все-таки це було б простішим тестом, ніж циклічним.
mikeserv

7

Протягом багатьох років я використовував щось подібне, щоб мати справу з різними місцями Bash в системах, для чого мені потрібні були мої сценарії.

Bash / Zsh / тощо.

#!/bin/sh

# Determines which OS and then reruns this script with approp. shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/sun5/bash";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  $SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
elif [ $OS_TYPE = "Linux" ]; then
  $LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
  echo "UNKNOWN OS_TYPE, $OS_TYPE";
  exit 1;
fi
exit 0;

### BEGIN

...script goes here...

Вищезазначене може бути легко адаптоване для різних перекладачів. Ключовим моментом є те, що цей сценарій спочатку працює як оболонка Bourne. Потім рекурсивно називає себе вдруге, але розбирає все вище за коментар, ### BEGINвикористовуючи sed.

Perl

Ось подібний трюк для Perl:

#!/bin/sh

LIN_PERL="/usr/bin/perl";
SOL_PERL="/packages/perl/bin/perl";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  eval 'exec $SOL_PERL -x -S $0 ${1+"$@"}';
elif [ $OS_TYPE = "Linux" ]; then
  eval 'exec $LIN_PERL -x -S $0 ${1+"$@"}';
else
  echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
  exit 0;
fi
exit 0;

#!perl

...perl script goes here...

Цей метод використовує можливість Perl, коли даний файл запускається, буде аналізувати зазначений файл, пропускаючи всі рядки, що знаходяться перед рядком #! perl.


Ряд питань там: відсутні котирування, використання $*замість "$@", марне використання eval, статус виходу не повідомляється (ви не використовували execперший), відсутні -/ --, повідомлення про помилки не на stderr, 0 статус виходу за умови помилки , використовуючи / bin / sh для LIN_BASH, марну крапку з комою (косметичну), використовуючи всі великі регістри для змінних, що не мають env. uname -sце якuname (uname - це ім'я Unix). Ви забули згадати, що пропуск викликається -xопцією до perl.
Стефан Хазелас

4

ПРИМІТКА: @ 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>-, а повинна зберігати визначення як єдиний, буквальний рядок і виконувати його як спеціальну вбудовану оболонку, коли її вимагають.

Таким чином, функція оболонки, оголошена у виконавчому скрипті оболонки, зберігається в пам'яті оболонки інтерпретації у її буквальному рядковому вигляді - нерозширюється, щоб включити додані тут документи як вхідні дані - і виконується незалежно від його вихідного файлу кожного разу, коли вона викликається як оболонка, протягом тих пір, поки триває поточне середовище оболонки.

А <<HERE-DOCUMENTЄ INLINE ФАЙЛА

Оператори перенаправлення <<і <<-обидва дозволяють перенаправляти рядки, що містяться у вхідному файлі оболонки, відомому як тут-документ, на вхід команди.

Цей документ розглядається як одне слово, яке починається після наступного \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

Буває таке:

  1. Я спочатку catвміст будь-який файл оболонки , створений для FILEв./file , зробити його виконуваним, а потім виконати його.

  2. Ядро інтерпретує #!і виклики /usr/bin/shз <read дескриптором файлу , призначеним ./file.

  3. shвідображає рядок у пам'ять, що складається з складної команди, що починається на _fn()і закінчується в SCRIPT.

  4. Коли _fnвикликається, shспочатку слід інтерпретувати, а потім відобразити в дескрипторі файл, визначений в ньому, <<SCRIPT...SCRIPT перш ніж викликати _fnяк спеціальну вбудовану утиліту, оскільки SCRIPTце_fn «s<input.

  5. Виведення рядка з допомогою printfі commandвиписаний на _fn«s стандартний вихід >&1 - який перенаправляється на поточному оболочечном ARGV0- або$0 .

  6. catоб'єднує <&0 стандартний вхідний файл-дескриптор - SCRIPT- над аргументом >усіченої поточної оболонки ARGV0, або $0.

  7. Виконуючи свою вже прочитану в поточній складовій команді , 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синхронізації. З цього моменту він отримав правильний шлях у рядку #!

Зараз майже все це є просто пух. Для цього безпечно вам потрібно:

  1. Функція, визначена вгорі і викликана внизу, яка виконує написання. Таким чином ми зберігаємо все необхідне в пам'яті і забезпечуємо зачитування всього файлу, перш ніж ми починаємо писати його.

  2. Якийсь спосіб визначення, яким повинен бути шлях. command -vдосить добре для цього.

  3. Гередоки справді допомагають, оскільки вони фактичні файли. Тим часом вони зберігатимуть ваш сценарій. Ви також можете використовувати рядки, але ...

  4. Ви повинні переконатися, що оболонка читає команду, яка перезаписує ваш скрипт у тому ж списку команд, що і той, що виконує його.

Подивіться:

{   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\\...

Сценарій вийшов так, як і мав бути - здебільшого тому, що це все було в гередоці - але якщо ви не плануєте його правильно, ви можете усікати свій файл файлів, що саме сталося зі мною вище.


Основна причина полягає в тому, що самовиправляючий код зазвичай вважається поганою практикою. Ще в часи крихітних програм складання це був розумний спосіб зменшити умовні гілки та підвищити продуктивність, але в наш час ризики для безпеки переважують переваги. Ваш підхід не працював, якби користувач, який керував сценарієм, не мав привілеїв для запису сценарію.
jw013

@ jw013 Очевидно, що мій підхід до встановлення або оновлення виконуваного сценарію не працював, якби особа, яка намагалася встановити або оновити скрипт , не мала дозволу на встановлення або оновлення сценарію. насправді саме це робить цю відповідь кращою, ніж будь-яка інша відповідь тут - вона може забезпечити точну рядок удару #! у міру необхідності та потребує лише спеціальних дозволів для цього під час першого виклику - під час встановлення. І, знову ж таки, я не буду просто приймати ваше слово за те, що самовиправляючий код - це погана практика - будь ласка, зверніться man commandдо суперечливої ​​думки.
mikeserv

будь ласка, зверніться man commandдо суперечливої ​​думки - не знайшовши її. Чи можете ви направити мене до конкретного розділу / абзацу, про який ви говорили?
jw013

@ jw013 - моя помилка, вона в man shпошуку - команда -v. Я знав, що це було на одній із manсторінок, на яку я дивився днями.
mikeserv

Я припускаю , що це є command -vприкладом ви говорили з man sh. Це нормальний сценарій інсталятора, а не самомодифікуючий . Навіть автономні інсталятори містять лише вхід попередньої модифікації та виводять свої зміни десь в іншому місці. Вони не переписують себе так, як ви рекомендуєте.
jw013

1

Ось один із способів створити сценарій самовиправлення, який виправить його шебанг. Цей код повинен бути попередньо до вашого фактичного сценарію.

#!/bin/sh
# unpatched

PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
  [ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
  cp -- "$0" "$0.org" || exit 1
  mv -- "$0" "$0.old" || exit 1
  (
    echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`" 
    sed -n '/^##/,$p' $0.old
  ) > $0 || exit
  chmod +x $0
  rm $0.old
  sync
  exit
fi
## Original script starts here

Деякі коментарі:

  • Його слід запустити один раз тим, хто має права створювати та видаляти файли в каталозі, де знаходиться сценарій.

  • Він використовує лише застарілий синтаксис оболонки Bourne, оскільки, незважаючи на поширену думку, /bin/shне гарантується, що це оболонка POSIX, навіть в сумісних з POSIX ОС.

  • Він встановив PATH сумісний з POSIX, а потім список можливих локацій zsh, щоб уникнути вибору "фальшивого" zsh.

  • Якщо з якихось причин не змінюється сценарій самовиправлення, було б тривіально розповсюджувати два сценарії замість одного, перший - той, який потрібно виправити, а другий - той, який я запропонував трохи змінити для обробки першого.


Справа /bin/shхороша - але в цьому випадку вам взагалі потрібна попередня модифікація #!? І хіба це не awkтак вже й шалено, як zshце?
mikeserv

@mikeserv Відповідь оновлено, щоб викликати POSIX awk. Попередньо модифікований шебанг є для того, щоб запобігти інтерпретації сценарію не сумісною оболонкою, якщо це оболонка для входу.
jlliagre

Має сенс. Я підтримав це, тому що він працює, він дотримується книги, і він демонструє здорове розуміння можливого середовища оболонки / обробки файлів - особливо файлів резервного копіювання, які ви використовуєте, що все, що GNU sed -iробить у будь-якому випадку. Я особисто думаю, що $PATHпроблему, зазначену в коментарях до іншої відповіді, і яку ви вирішуєте так само безпечно, наскільки я можу зрозуміти тут, в декількох рядках, краще вирішити шляхом простого і чіткого визначення залежностей та / або суворого та явного тестування - наприклад, зараз це getconfможе бути фальшивий, але шанси близькі до нуля, так само , як вони були для zshіawk.
mikeserv

@mikeserv, скрипт модифікований, щоб зменшити ризик викликати фальшивий getconf.
jlliagre

$(getconf PATH)це не Борн. cp $0 $0.oldє синтаксисом zsh. Еквівалент Борна хотів cp "$0" "$0.old"би хотітиcp -- "$0" "$0.old"
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.