У меня есть скрипт, который я хочу запускать на двух машинах. Эти две машины получают копии скрипта из одного и того же 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
?
env
нет ни в / bin, ни в / usr / bin? Попробуйтеwhich -a env
подтвердить.Ответы:
Вы не можете решить это с помощью Шебанга напрямую, поскольку Шебанг является чисто статическим. То, что вы могли бы сделать, это иметь некоторый «наименьший общий множитель» (с точки зрения оболочки) в shebang и повторно выполнить ваш скрипт с подходящей оболочкой, если этот LCM не zsh. Другими словами: ваш скрипт должен выполняться оболочкой во всех системах, проверить наличие
zsh
функции -only и, если тест окажется ложным, запустите скриптexec
сzsh
, где тест будет успех , и вы просто продолжать.zsh
Например, одной уникальной особенностью является наличие$ZSH_VERSION
переменной:В этом простом случае скрипт сначала выполняется
/bin/sh
(все Unix-подобные системы после 80-х понимают#!
и имеют/bin/sh
либо Bourne, либо POSIX, но наш синтаксис совместим с обоими). Если$ZSH_VERSION
это не установлено, сценарийexec
«S себя черезzsh
. Если$ZSH_VERSION
установлено (соответственно, скрипт уже запущенzsh
), тест просто пропускается. Вуаля.Это только терпит неудачу, если
zsh
не в$PATH
вообще.Edit: Для того, чтобы убедиться, вы только в обычных местах, вы могли бы использовать что - то вроде
exec
zsh
Это может уберечь вас от случайного
exec
попадания чего-то в вас,$PATH
чегоzsh
вы не ожидаете.источник
zsh
в$PATH
не один вы ожидаете.zsh
двоичный файл в стандартных местоположениях действительноzsh
.zsh
себя, где этоzsh -c 'whence zsh'
. Проще просто можноcommand -v zsh
. Смотрите мой ответ о том, как динамически путь#!bang
.zsh
двоичного файла$PATH
для получения путиzsh
двоичного файла не совсем решит проблему, на которую указал @RyanReich, не так ли? :)zsh
себя, нет, я думаю, нет. Но если вы встраиваете полученную строку в свой хэш-код, а затем исполняете свой собственный скрипт, вы, по крайней мере, знаете, что получаете. Тем не менее, это сделало бы более простой тест, чем зацикливание.В течение многих лет я использовал что-то подобное, чтобы иметь дело с различными местами Bash в системах, для которых мне нужны были мои скрипты.
Bash / Zsh / и т.д..
Вышесказанное может быть легко адаптировано для различных переводчиков. Ключевым моментом является то, что этот скрипт изначально запускается как оболочка Bourne. Затем он рекурсивно вызывает себя второй раз, но анализирует все выше комментария,
### BEGIN
используяsed
.Perl
Вот похожий трюк для Perl:
Этот метод использует способность Perl, когда данный файл для запуска будет анализировать указанный файл, пропуская все строки перед строкой
#! perl
.источник
$*
вместо"$@"
, бесполезное использование eval, состояние выхода не сообщается (вы не использовалиexec
первый), отсутствие-
/--
, сообщения об ошибках не в stderr, 0 состояние выхода для условий ошибки , используя / bin / sh для LIN_BASH, бесполезная точка с запятой (косметическая), используя все заглавные буквы для переменных не env.uname -s
это какuname
(uname для имени Unix). Вы забыли упомянуть, что пропуск вызывается-x
опциейperl
.ПРИМЕЧАНИЕ: @ jw013 высказывает следующие неподдерживаемые возражения в комментариях ниже:
Я ответил на его возражения безопасности, указывая на то , что какие - либо специальные разрешения требуется только один раз за установки / обновления действия для того , чтобы установить / обновить в самоустанавливающийся сценарий - который я лично называю довольно безопасным. Я также указал ему на
man sh
ссылку на достижение аналогичных целей с помощью аналогичных средств. В то время я не удосужился указать, что какие бы ни были недостатки в безопасности или вообще неучтенные действия, которые могут или не могут быть представлены в моем ответе, они были скорее укоренены в самом вопросе, чем в моем ответе на него:Не удовлетворенный, @ jw013 продолжал возражать, дополняя свой пока еще не поддерживаемый аргумент хотя бы парой ошибочных утверждений:
На первом месте:
ЕДИНСТВЕННАЯ EXECUTABLE КОД В ЛЮБОЙ EXECUTABLE SHELL SCRIPT ЯВЛЯЕТСЯ
#!
ОБОЛОЧКИ(хотя даже
#!
это официально не определено )Сценарий оболочки это просто текстовый файл - для того , чтобы иметь какой - либо эффект на всем это должно быть прочитать другой исполняемый файл, его инструкция затем интерпретирована этим другим исполняемым файлом, пока , наконец , другой исполняемый файл , то выполняет свою интерпретацию из сценарий оболочки. При выполнении файла сценария оболочки невозможно задействовать менее двух файлов. Существует возможное исключение в
zsh
собственном компиляторе, но с этим у меня мало опыта, и он никоим образом не представлен здесь.Hashbang сценария оболочки должен указывать на предполагаемый интерпретатор или отбрасываться как не относящийся к делу.
Оболочечный ЗНАК ПРИЗНАНИЕ / ИСПОЛНЕНИЕ ПОВЕДЕНИЯ СТАНДАРТОВ ОПРЕДЕЛЕННЫЕ
Оболочка имеет два основных режима синтаксического анализа и интерпретации своих входных данных: либо ее текущий ввод определяет,
<<here_document
либо он определяет{ ( command |&&|| list ) ; } &
- другими словами, оболочка или интерпретирует токен как разделитель для команды, которую она должна выполнить, как только прочитает ее. в или как инструкции для создания файла и сопоставления его с дескриптором файла для другой команды. Вот и все.При интерпретации команд для выполнения оболочка разграничивает токены на набор зарезервированных слов. Когда оболочка сталкивается с открывающим токеном, она должна продолжать читать в списке команд, пока список не будет ограничен либо закрывающим токеном, таким как символ новой строки (если применимо), либо закрывающим токеном, например,
})
для({
перед выполнением.Оболочка различает простую команду и составную команду. Соединение команда представляет собой набор команд , которые должны быть считаны в перед выполнением, но оболочка не выполняет
$expansion
ни на одном из его составных команд простых до тех пор, пока по отдельности выполняет каждый из них.Итак, в следующем примере
;semicolon
зарезервированные слова разделяют отдельные простые команды, тогда как не экранированный\newline
символ разделяет две составные команды:Это упрощение руководящих принципов. Это становится намного сложнее, когда вы рассматриваете встроенные оболочки, подоболочки, текущую среду и т. Д., Но для моих целей здесь этого достаточно.
И если говорить о встроенных модулях и командных списках,
function() { declaration ; }
является лишь средством назначения соединения команды к простой команде. Оболочка не должна выполнять никаких$expansions
действий в самом объявлении - для включения<<redirections>
- но должна вместо этого хранить определение в виде одной буквенной строки и выполнять ее как специальную встроенную оболочку при вызове.Таким образом, функция оболочки, объявленная в исполняемом скрипте оболочки, хранится в памяти интерпретирующей оболочки в ее буквальной строковой форме - нерасширенной, чтобы включать добавленные здесь документы в качестве входных данных - и выполняется независимо от своего исходного файла каждый раз, когда она вызывается как встроенная оболочка. в течение всего текущего времени оболочки.
А это
<<HERE-DOCUMENT
встроенный файлВы видите? Для каждой оболочки выше оболочки создает файл и сопоставляет его с дескриптором файла. В
zsh, (ba)sh
оболочке создает обычный файл в/tmp
, выводит выходные данные, сопоставляет его с дескриптором, затем удаляет/tmp
файл, чтобы копия дескриптора ядра была всем, что осталось.dash
избегает всей этой чепухи и просто отбрасывает свою обработку вывода в анонимный|pipe
файл, направленный на<<
цель перенаправления .Это делает
dash
:функционально эквивалентно
bash
's:в то время как
dash
реализация «S, по крайней мере POSIXly портативный.ЧТО ДЕЛАЕТ НЕСКОЛЬКО ФАЙЛОВ
Так что в ответе ниже, когда я делаю:
Происходит следующее:
Я первое
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 exec
S исполняемый - и вновь переписан -$0
аргумент.С момента
./file
вызова до тех пор, пока содержащиеся в нем инструкции не укажут, что он должен бытьexec
снова d,sh
считывает его в виде одной составной команды за раз, когда он их выполняет, хотя./file
сам по себе ничего не делает, кроме как с радостью принимает свое новое содержимое. Файлы, которые на самом деле на работе/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
СПАСИБО, ПОСЛЕ ВСЕХ
Поэтому, когда @ jw013 указывает, что:
... среди его ошибочной критики этого ответа, он на самом деле невольно потворствует единственному методу, используемому здесь, который в основном работает так:
ОТВЕТ
Все ответы здесь хорошие, но ни один из них не является полностью правильным. Кажется, что все утверждают, что вы не можете динамически и постоянно управлять своим
#!bang
. Вот демонстрация установки независимого пути:DEMO
ВЫХОД
Вы видите? Мы просто заставляем скрипт перезаписывать себя. И это происходит только один раз после
git
синхронизации. С этого момента он получил правильный путь в строке #! Bang.Теперь почти все это просто пух. Для этого вам необходимо:
Функция, определенная вверху и вызываемая внизу, которая выполняет запись. Таким образом, мы сохраняем все необходимое в памяти и гарантируем, что весь файл будет прочитан, прежде чем мы начнем перезаписывать его.
Какой-то способ определить, каким должен быть путь.
command -v
довольно хорошо для этого.Heredocs действительно помогают, потому что они настоящие файлы. Тем временем они сохранят ваш сценарий. Вы можете использовать строки, но ...
Вы должны убедиться, что оболочка считывает команду, которая перезаписывает ваш скрипт в том же списке команд, что и тот, который его исполняет.
Посмотрите:
Обратите внимание, что я переместил
exec
команду только на одну строку. Сейчас:Я не получаю вторую половину вывода, потому что сценарий не может прочитать следующую команду. Тем не менее, потому что единственная отсутствующая команда была последней:
Сценарий был выполнен так, как и должен был - в основном потому, что все было в наследственности - но если вы не спланируете его правильно, вы можете обрезать свой файловый поток, что и случилось со мной выше.
источник
man command
Противоречивое мнение.man command
на противоречивое мнение - не найти. Можете ли вы направить меня к конкретному разделу / параграфу, о котором вы говорили?man sh
- поиск «команды -v». Я знал, что это было на одной изman
страниц, которые я смотрел на другой день.command -v
примером вы говорили сman sh
. Это обычный установочный скрипт, а не самоизменяющийся . Даже автономные установщики содержат только ввод перед изменением и выводят свои модификации куда-то еще. Они не переписывают себя так, как вы рекомендуете.Вот один из способов получить самоизменяющийся скрипт, который исправит его шебанг. Этот код должен быть добавлен к вашему фактическому сценарию.
Некоторые комментарии:
Он должен запускаться один раз тем, у кого есть права на создание и удаление файлов в каталоге, где расположен скрипт.
Он использует только унаследованный синтаксис оболочки Bourne, поскольку, несмотря на распространенное мнение,
/bin/sh
он не гарантированно является оболочкой POSIX, даже в операционных системах, совместимых с POSIX.Он установил PATH на POSIX-совместимый, а затем список возможных местоположений zsh, чтобы избежать выбора «фальшивого» zsh.
Если по какой-то причине самоизменяющийся сценарий нежелателен, было бы тривиально распределить два сценария вместо одного, причем первый - это тот, который вы хотите исправить, а второй - предложенный мной, слегка измененный для обработки первого.
источник
/bin/sh
том, что это хороший вопрос, но в таком случае вам вообще нужна предварительная модификация#!
? И неawk
так ли вероятно быть фальшивым, какzsh
есть?sed -i
любом случае делает GNU . Я лично считаю, что$PATH
проблема, отмеченная в комментариях к другому ответу и которую вы решаете настолько безопасно, насколько я могу выразить здесь в нескольких строках, лучше решается простым и явным определением зависимостей и / или строгим и явным тестированием - например, теперьgetconf
может быть фальшивый, но шансы близки к нулю, так же , как они были дляzsh
иawk.
$(getconf PATH)
это не Борн.cp $0 $0.old
это синтаксис Zsh. Эквивалентом Борна было быcp "$0" "$0.old"
то, что вы хотелиcp -- "$0" "$0.old"