Путь независимых шебангов

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

Как я могу настроить shebang так, чтобы при запуске скрипта /path/to/script.shвсегда использовались Zshдоступные в PATH?

Амелио Васкес-Рейна
источник
8
Вы уверены, что envнет ни в / bin, ни в / usr / bin? Попробуйте which -a envподтвердить.
user1686

Ответы:

22

Вы не можете решить это с помощью Шебанга напрямую, поскольку Шебанг является чисто статическим. То, что вы могли бы сделать, это иметь некоторый «наименьший общий множитель» (с точки зрения оболочки) в shebang и повторно выполнить ваш скрипт с подходящей оболочкой, если этот LCM не zsh. Другими словами: ваш скрипт должен выполняться оболочкой во всех системах, проверить наличие zshфункции -only и, если тест окажется ложным, запустите скрипт 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 ), тест просто пропускается. Вуаля.

Это только терпит неудачу, если 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вы не ожидаете.

Андреас Визе
источник
Я upvoted это за элегантность , но это делает, в принципе, есть вопросы безопасности / совместимости, если первый 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 ссылку на достижение аналогичных целей с помощью аналогичных средств. В то время я не удосужился указать, что какие бы ни были недостатки в безопасности или вообще неучтенные действия, которые могут или не могут быть представлены в моем ответе, они были скорее укоренены в самом вопросе, чем в моем ответе на него:

Как я могу настроить shebang так, чтобы при запуске сценария как /path/to/script.sh всегда использовался Zsh, доступный в PATH?

Не удовлетворенный, @ jw013 продолжал возражать, дополняя свой пока еще не поддерживаемый аргумент хотя бы парой ошибочных утверждений:

Вы используете один файл, а не два файла. Пакет [ man shссылочный] содержит один файл для изменения другого файла. У вас есть файл, модифицирующий себя. Между этими двумя случаями есть четкая разница. Файл, который принимает входные данные и производит выходные данные, в порядке. Исполняемый файл, который изменяется сам по себе во время работы, обычно является плохой идеей. Пример, на который вы указали, не делает этого.

На первом месте:

ЕДИНСТВЕННАЯ EXECUTABLE КОД В ЛЮБОЙ EXECUTABLE SHELL SCRIPT ЯВЛЯЕТСЯ#! ОБОЛОЧКИ

(хотя даже #!это официально не определено )

{   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 собственном компиляторе, но с этим у меня мало опыта, и он никоим образом не представлен здесь.

Hashbang сценария оболочки должен указывать на предполагаемый интерпретатор или отбрасываться как не относящийся к делу.

Оболочечный ЗНАК ПРИЗНАНИЕ / ИСПОЛНЕНИЕ ПОВЕДЕНИЯ СТАНДАРТОВ ОПРЕДЕЛЕННЫЕ

Оболочка имеет два основных режима синтаксического анализа и интерпретации своих входных данных: либо ее текущий ввод определяет, <<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встроенный файл

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

Здесь-документ должен рассматриваться как одно слово , которое начинается после следующего \newlineи продолжается до тех пор , пока не будет строка , содержащая только разделитель и А \newline, без каких - либо [:blank:]с между ними. Затем начинается следующий документ здесь , если он есть. Формат выглядит следующим образом:

[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реализация «S, по крайней мере 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вызова до тех пор, пока содержащиеся в нем инструкции не укажут, что он должен быть execснова d, 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синхронизации. С этого момента он получил правильный путь в строке #! Bang.

Теперь почти все это просто пух. Для этого вам необходимо:

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

  2. Какой-то способ определить, каким должен быть путь. command -vдовольно хорошо для этого.

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

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

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

mikeserv
источник
Недостаток в том, что самоизменяющийся код обычно считается плохой практикой. В старые времена крошечных программ сборки это был умный способ уменьшить условные переходы и повысить производительность, но в настоящее время риски безопасности перевешивают преимущества. Ваш подход не сработает, если пользователь, запустивший сценарий, не имеет прав на запись в сценарий.
jw013
@ jw013 Очевидно, что мой подход к установке или обновлению исполняемого скрипта не будет работать, если у человека, пытающегося установить или обновить скрипт , не было разрешений на установку или обновление скрипта. на самом деле, именно это делает этот ответ лучше, чем любой другой ответ здесь - он может предоставить точную строку #! bang по мере необходимости и требует только специальных разрешений для этого при первом вызове - во время установки. И, опять же, я не просто скажу вам, что модификация кода является плохой практикой - см. 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.

  • Если по какой-то причине самоизменяющийся сценарий нежелателен, было бы тривиально распределить два сценария вместо одного, причем первый - это тот, который вы хотите исправить, а второй - предложенный мной, слегка измененный для обработки первого.

jlliagre
источник
Дело в /bin/shтом, что это хороший вопрос, но в таком случае вам вообще нужна предварительная модификация #!? И не awkтак ли вероятно быть фальшивым, как zshесть?
mikeserv
@mikeserv Ответ обновлен для вызова POSIX awk. Предопределенный shebang предназначен для предотвращения интерпретации сценария не совместимой с bourne оболочкой, если это будет ваша оболочка входа в систему.
Jlliagre
Имеет смысл. Я проголосовал за него, потому что он работает, он прилипает к книге и демонстрирует хорошее понимание возможной среды оболочки / обработки файлов - особенно файлов резервных копий, которые вы используете, что в sed -iлюбом случае делает GNU . Я лично считаю, что $PATHпроблема, отмеченная в комментариях к другому ответу и которую вы решаете настолько безопасно, насколько я могу выразить здесь в нескольких строках, лучше решается простым и явным определением зависимостей и / или строгим и явным тестированием - например, теперь getconfможет быть фальшивый, но шансы близки к нулю, так же , как они были для zshиawk.
mikeserv
@mikeserv, скрипт изменен, чтобы уменьшить риск вызова фиктивного getconf.
Jlliagre
$(getconf PATH)это не Борн. cp $0 $0.oldэто синтаксис Zsh. Эквивалентом Борна было бы cp "$0" "$0.old"то, что вы хотелиcp -- "$0" "$0.old"
Стефан Шазелас