Как использовать `/ usr / bin / env sed -f` в shebang?

25

Набор текста /usr/bin/env sed -fв терминале работает.

Но если использовать его как шебанг,

#!/usr/bin/env sed -f 
s/a/b/

Сценарий не будет выполнен:

/usr/bin/env: sed -f: No such file or directory

Я верю, что это связано с -f. Но как решить это?

Cheng
источник
stackoverflow.com/questions/4303128/…
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件

Ответы:

33

Вы не можете, переносимым, поставить более одного аргумента в #!строке . Это означает только полный путь и один аргумент (например, #!/bin/sed -fили #!/usr/bin/sed -f), или #!/usr/bin/envнет аргумента интерпретатору.

Обходной путь для получения переносимого скрипта - использовать #!/bin/shоболочку оболочки, передавая скрипт sed в качестве аргумента командной строки. Обратите внимание, что это не разрешается POSIX (сценарии с несколькими инструкциями должны быть написаны с отдельным -eаргументом для каждой инструкции для переносимости), но это работает со многими реализациями.

#!/bin/sh
exec sed '
s/a/b/
' "$@"

Для длинного сценария может быть удобнее использовать heredoc. Преимущество heredoc в том, что вам не нужно заключать в кавычки одинарные кавычки, если они есть. Основным недостатком является то, что сценарий подается на sed на своем стандартном вводе, с двумя досадными последствиями. Некоторые версии sed требуют -f /dev/stdinвместо этого -f -, что является проблемой для переносимости. Хуже того, скрипт не может выступать в качестве фильтра, потому что стандартным вводом является скрипт и он не может быть данными.

#!/bin/sh
exec sed -f - -- "$@" <<'EOF'
s/a/b/
EOF

Недостаток heredoc может быть исправлен путем полезного использования cat. Так как это снова помещает весь сценарий в командную строку, он не POSIX-совместимый, но практически переносимый на практике.

#!/bin/sh
exec sed "$(cat <<'EOF')" -- "$@"
s/a/b/
EOF

Другой обходной путь - написать скрипт, который может быть проанализирован как sh, так и sed. Это портативный, достаточно эффективный, просто немного уродливый.

#! /bin/sh
b ()
{
x
}
i\
f true; then exec sed -f "$0" "$@"; fi
: ()
# sed script starts here
s/a/b/

Пояснения:

  • Под sh: определить вызываемую функцию b; содержимое не имеет значения, пока функция синтаксически правильно сформирована (в частности, у вас не может быть пустой функции). Затем, если true (т.е. всегда), выполнить sedна сценарии.
  • Под sed: ответвление к ()метке, затем некоторый правильно сформированный ввод. Затем iкоманда, которая не имеет никакого эффекта, потому что она всегда пропускается. Наконец за ()меткой следует полезная часть скрипта.
  • Протестировано под GNU sed, BusyBox и OpenBSD. (В GNU sed вы можете избежать неприятностей, но OpenBSD sed требователен к тем частям, которые он пропускает.)
Жиль "ТАК - перестань быть злым"
источник
1
«или #!/usr/bin/envи без аргументов». не очень хорошо сформулировано. Возможно, «или #!/usr/bin/env sedнет аргументов для сед».
CJM
+1 за «просто немного некрасивый» и
двухъязычный
6

Существуют различные несовместимые реализации shebang (#!) В зависимости от ОС. Некоторые создают полный список аргументов, некоторые сохраняют путь команды и помещают все оставшиеся аргументы как один, некоторые игнорируют все аргументы и передают только путь команды, и, наконец, некоторые передают всю строку как одну команда. Вы, кажется, в последнем случае.

jlliagre
источник
2

env пытается найти файл с именем "sed -f". Вы можете попробовать "#! / Usr / bin / sed -f" в качестве строки shebang.

anilmwr
источник
2

Начиная с GNU coreutils v8.30 , вы можете делать:

#!/usr/bin/env -S sed -f

Эта функция была добавлена в недавнем (2018-04-20) совершить , чтобы env.cв ГНУ Coreutils пакет, который добавил к -Sили --split-stringвариант.

Со envстраницы руководства :

OPTIONS
-S/--split-string usage in scripts
    The  -S  option allows specifing multiple parameters in a script.
    Running a script named 1.pl containing the following first line:

            #!/usr/bin/env -S perl -w -T

    Will execute perl -w -T 1.pl .

    Without the '-S' parameter the script will likely fail with:

            /usr/bin/env: 'perl -w -T': No such file or directory

    See the full documentation for more details.

Дополнительные примеры доступны в руководстве по GNU coreutils .

Если вы также используете -vопцию для подробного вывода, вы можете увидеть, как именно envразбивает строку аргументов:

В my_sed_script.sed:

#!/usr/bin/env -vS sed -f
s/a/b/

Исполнение:

$ ./my_sed_script.sed
split -S:  ‘sed -f’
 into:    ‘sed’
     &    ‘-f’
executing: sed
   arg[0]= ‘sed’
   arg[1]= ‘-f’
   arg[2]= ‘./my_sed_script.sed’

Примечание. Это относится только к используемым шебангами /usr/bin/env, так как --split-stringэто особенность GNU env.

Амин Месба
источник
1

Этот ответ предоставляет путь к элегантному решению: /programming//a/1655389/642372

  1. read наследник в переменную оболочки.
  2. Передайте эту переменную в качестве позиционного аргумента sed.

Образец:

#!/usr/bin/env bash

read -rd '' SED_SCRIPT <<EOD      
# Your sed script goes here.
s/a/b/
EOD

exec sed "$SED_SCRIPT" "$@"
Уокер Хейл IV
источник
1

Мне нравится решение Уокера, но его можно улучшить (поместив его в отдельный ответ, потому что комментарии не принимают предварительно отформатированный текст). Это может не работать на всех версиях Linux или Bash, но в Ubuntu 17.10 вы можете сократить это до следующего:

#!/usr/bin/env bash
read SED_SCRIPT <<EOD
s/a/b/
EOD
sed "$SED_SCRIPT" "$@"

Вы можете удалить evalи упростить readкоманду, но вы должны избавиться от комментария внутри heredoc.

Кроме того, для всего, что вы когда-либо хотели знать о sed, но боялись спросить, есть очень полезное руководство по адресу http://www.grymoire.com/unix/sed.html . Это предлагает еще лучшее решение за счет потери /usr/bin/envи жесткого кодирования пути к sed:

#!/bin/sed -f
s/a/b/
s/c/d/

Это имеет дополнительное преимущество поддержки более чем одной s///замены.

Хью Уолтерс
источник