A maneira de usar '/ usr / bin / env sed -f' em shebang?

22

Digitando /usr/bin/env sed -f em trabalhos de terminal.

Mas se você usar isso como um shebang,

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

O script não será executado:

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

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

    
por Cheng 13.06.2011 / 05:53

6 respostas

31

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

Uma solução alternativa para obter um script portátil é usar #!/bin/sh e um wrapper de shell, passando o script sed como um argumento de linha de comando. Note que isto não é sancionado pelo POSIX (scripts multi-instrução devem ser escritos com um argumento -e separado para cada instrução para 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 internas, se houver. Uma grande desvantagem é que o script é alimentado com base em sua entrada padrão, com duas conseqüências incômodas. Algumas versões do sed requerem -f /dev/stdin em vez de -f - , o que é um problema para portabilidade. Pior, o script não pode funcionar 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 por 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 é escrever um script que possa ser analisado tanto por sh como por sed. Isso é portátil, razoavelmente eficiente, 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:

  • Em 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 for verdade (ou seja, sempre), execute sed no script.
  • Under sed: ramifique para o rótulo () e, em seguida, para alguma entrada bem formada. Em seguida, um comando i , que não tem efeito porque é sempre ignorado. Finalmente, o rótulo () seguido pela parte útil do script.
  • Testado sob o GNU sed, BusyBox e OpenBSD. (Você pode se safar com algo mais simples no GNU sed, mas o OpenBSD sed é exigente quanto às partes que ele pula.)
por 13.06.2011 / 09:18
6

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

    
por 13.06.2011 / 08:02
2

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

    
por 13.06.2011 / 07:26
1

Esta resposta fornece um caminho para uma solução elegante: link

  1. read a heredoc em uma variável de shell.
  2. Passe essa variável como um 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" "$@"
    
por 17.05.2018 / 03:16
1

A partir do GNU coreutils v8.30 , você pode fazer:

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

Esta funcionalidade foi adicionada recentemente (2018-04-20) commit para env.c no Pacote GNU coreutils, que adicionou a opção -S ou --split-string .

Na página env man:

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 GNU coreutils manual .

Se você também usa a opção -v para saída detalhada, pode ver exatamente como env divide a string 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 só se aplica a shebangs que usam /usr/bin/env , como --split-string é uma característica do GNU env especificamente.

    
por 11.11.2018 / 22:32
0

Eu 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 isso ao seguinte:

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

Você pode remover o eval e simplificar o comando read , mas você precisa se livrar do comentário dentro do heredoc.

Além disso, para tudo que você sempre quis saber sobre sed, mas teve medo de perguntar, há um tutorial muito útil no link . Isso sugere uma solução ainda melhor, à custa de perder o /usr/bin/env e codificar o caminho para o sed:

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

Isso tem a vantagem adicional de suportar mais de um% de substituição des///.

    
por 09.10.2018 / 09:12