A maneira de usar `/ usr / bin / env sed -f` no shebang?

25

Digitando /usr/bin/env sed -ftrabalhos terminais.

Mas se usá-lo como um shebang,

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

O script falhará ao executar:

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

Eu meio que acredito que isso esteja relacionado ao -f. Mas como resolver isso?

Cheng
fonte
stackoverflow.com/questions/4303128/…
Ciro Santilli escreveu:

Respostas:

33

Você não pode, de maneira portável, colocar mais de um argumento em uma #!linha . Isso significa apenas um caminho completo e um argumento (por exemplo, #!/bin/sed -fou #!/usr/bin/sed -f) ou #!/usr/bin/envnenhum argumento para o intérprete.

Uma solução alternativa para obter um script portátil é usar #!/bin/shum invólucro de shell, passando o script sed como um argumento da linha de comando. Observe que isso não é sancionado pelo POSIX (scripts de várias instruções devem ser escritos com um -eargumento separado para cada instrução de portabilidade), mas funciona com muitas implementações.

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

Para um script longo, pode ser mais conveniente usar um heredoc. Uma vantagem de um heredoc é que você não precisa citar as aspas simples, se houver. Uma desvantagem importante é que o script é alimentado para sed em sua entrada padrão, com duas conseqüências irritantes. Algumas versões do sed requerem, em -f /dev/stdinvez de -f -, o que é um problema de portabilidade. Pior, o script não pode atuar como um filtro, porque a entrada padrão é o script e não pode ser os dados.

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

A desvantagem do heredoc pode ser remediada com um uso útil de cat. Como isso coloca o script inteiro na linha de comando novamente, ele não é compatível com POSIX, mas é amplamente portátil na prática.

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

Outra solução alternativa é escrever um script que possa ser analisado por sh e por sed. Isso é portátil, razoavelmente eficiente, apenas um pouco feio.

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

Explicações:

  • Sob sh: defina uma função chamada b; o conteúdo não importa, desde que a função esteja sintaticamente bem formada (em particular, você não pode ter uma função vazia). Então, se verdadeiro (ou seja, sempre), execute sedo script.
  • Sob sed: ramifique para o ()rótulo, em seguida, alguma entrada bem formada. Em seguida, um icomando, que não tem efeito, porque é sempre ignorado. Finalmente, o ()rótulo seguido pela parte útil do script.
  • Testado sob GNU sed, BusyBox e OpenBSD. (Você pode se safar de algo mais simples no GNU sed, mas o OpenBSD sed é exigente quanto às partes que pula.)
Gilles 'SO- parar de ser mau'
fonte
11
"ou #!/usr/bin/enve nenhum argumento." não é redigido muito bem. Talvez "ou #!/usr/bin/env sednenhum argumento para sed".
CJM
+1 para "apenas um pouco feio" e o script de dupla linguagem :)
retracile
6

Existem várias implementações incompatíveis do shebang (#!), Dependendo do sistema operacional. Alguns estão criando uma lista completa de argumentos, outros estão preservando o caminho do comando e colocando todos os argumentos restantes como um único, outros estão ignorando todos os argumentos e passam apenas o caminho do comando e, finalmente, alguns estão passando a string inteira como um único comando. Você parece estar no último caso.

jlliagre
fonte
2

env está tentando encontrar um arquivo com o nome "sed -f". Você pode tentar "#! / Usr / bin / sed -f" como sua linha shebang.

anilmwr
fonte
2

No GNU coreutils v8.30 , você pode:

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

Este recurso foi adicionado em uma recente (2018/04/20) comprometer a env.cno pacote do GNU coreutils, que acrescentaram a -Sou --split-stringopção.

Na envpágina do manual:

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.

Mais exemplos estão disponíveis no manual GNU coreutils .

Se você também usar a -vopção para saída detalhada, poderá ver exatamente como envdivide a sequência de argumentos:

Em my_sed_script.sed:

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

Execução:

$ ./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’

Nota: Isso se aplica apenas aos shebangs que usam /usr/bin/env, como --split-stringé um recurso do GNU envespecificamente.

Amin Mesbah
fonte
1

Esta resposta fornece um caminho para uma solução elegante: /programming//a/1655389/642372

  1. read um heredoc em uma variável de shell.
  2. Passe essa variável como argumento posicional para sed.

Amostra:

#!/usr/bin/env bash

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

exec sed "$SED_SCRIPT" "$@"
Walker Hale IV
fonte
1

Gosto da solução de Walker, mas ela pode ser melhorada (colocando isso em uma resposta separada, porque os comentários não aceitam texto pré-formatado). Isso pode não funcionar em todas as versões do Linux ou Bash, mas no Ubuntu 17.10 você pode reduzir o seguinte:

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

Você pode remover evale simplificar o readcomando, mas precisa se livrar do comentário dentro do heredoc.

Além disso, para tudo o que você sempre quis saber sobre o sed, mas teve medo de perguntar, há um tutorial muito útil em http://www.grymoire.com/unix/sed.html . Isso sugere uma solução ainda melhor, à custa de perder /usr/bin/enve codificar o caminho para sed:

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

Isso tem a vantagem adicional de suportar mais de uma s///substituição.

Huw Walters
fonte