Como posso conseguir portabilidade com sed -i (edição no local)?

37

Estou escrevendo scripts de shell para o meu servidor, que é um hosting compartilhado rodando o FreeBSD. Eu também quero ser capaz de testá-los localmente, no meu PC rodando Linux. Por isso, estou tentando escrevê-los de maneira portátil, mas com sed não vejo maneira de fazer isso.

Parte do meu site usa arquivos html estáticos gerados, e essa linha sed insere DOCTYPE correto após cada regeneração:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Ele funciona com GNU sed no Linux, mas o FreeBSD sed espera que o primeiro argumento após o sinalizador -i seja extensão para cópia de backup. É assim que ficaria:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

No entanto, GNU sed , por sua vez, espera que a expressão siga imediatamente após -i. (Também requer correções com manipulação de nova linha, mas isso já foi respondido em aqui )

É claro que posso incluir essa alteração na minha cópia do servidor do script, mas isso seria uma confusão, ou seja, meu uso de VCS para controle de versão. Existe uma maneira de conseguir isso com o sed de uma maneira totalmente portátil?

    
por Red 30.09.2013 / 01:31

7 respostas

34

O GNU sed aceita uma extensão opcional após -i . A extensão deve estar no mesmo argumento sem espaço intermediário. Esta sintaxe também funciona no BSD sed.

sed -i.bak -e '…' SOMEFILE

Observe que no BSD, -i também altera o comportamento quando há vários arquivos de entrada: eles são processados independentemente (por exemplo, $ corresponde à última linha de cada arquivo). Também isso não funcionará no BusyBox.

Se você não quiser usar arquivos de backup, verifique qual versão do sed está disponível.

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

Ou, como alternativa, para evitar a supressão dos parâmetros posicionais, defina uma função.

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

Se você não quer se incomodar, use o Perl.

perl -i -pe '…' "$file"

Se você quiser escrever um script portátil, não use -i - ele não está no POSIX. Faça manualmente o que o sed faz sob o capô - é apenas mais uma linha de código.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"
    
por 30.09.2013 / 03:39
10

Se você não encontrar um truque para tornar o sed legal, tente:

  1. Não use -i :

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
    
  2. Use Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"
    
por 30.09.2013 / 01:57
7

ed

Você sempre pode usar ed para preceder uma linha em um arquivo existente.

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

Detalhes

Os bits em torno do <!DOCTYPE html> são comandos para ed instruindo-o a adicionar essa linha ao arquivo my.html .

sed

Eu acredito que este comando em sed também pode ser usado:

$ sed -i '1i<!DOCTYPE html>\n' testfile.csv
    
por 30.09.2013 / 06:45
7

Você também pode fazer manualmente o que o perl -i faz sob o capô:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

Como perl -i , não há backup e, como a maioria das soluções dadas aqui, lembre-se de que isso pode afetar as permissões, a propriedade do arquivo e transformar um link simbólico em um arquivo normal.

Com:

sed '1i\
<!DOCTYPE html>' file 1<> file

sed sobrescreveria o arquivo, por isso não afetaria a propriedade e as permissões ou os links simbólicos. Ele funciona com o GNU sed porque sed normalmente leu um buffer cheio de dados de file (4k no meu caso) antes de sobrescrevê-lo com o comando i . Isso não funcionaria se o arquivo fosse maior que 4k, exceto pelo fato de que sed também armazena sua saída.

Basicamente sed funciona em blocos de 4k para leitura e escrita. Se a linha a ser inserida for menor que 4k, sed nunca irá sobrescrever um bloco que ainda não tenha lido.

Eu não contaria com isso.

    
por 30.09.2013 / 23:00
3

FreeBSD sed , que é usado no Mac OS X também, precisa do A opção -e após a mudança do -i para definir & reconhecer o seguinte comando (regex) corretamente & inequivocamente.

Em outras palavras, sed -i -e ... deve funcionar com o FreeBSD & GNU sed .

Mais geralmente, omitir a extensão de backup após o FreeBSD sed -i requer alguma opção sed explícita ou mudar após o -i para evitar confusão em parte do FreeBSD sed ao analisar seus argumentos de linha de comando.

(Observe, no entanto, que as edições de arquivo sed no local levam a alterações no arquivo inode, consulte " No local "edição de arquivos ).

(Como uma dica geral, versões recentes do FreeBSD sed possuem a opção -r para aumentar a compatibilidade com o GNU sed ).

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt
    
por 25.11.2013 / 17:56
1

Para emular sed -i para um único arquivo portavelmente, evitando ao máximo as condições de corrida:

sed 'script' <<FILE >file
$(cat file)
FILE

A propósito, isso também lida com o possível problema que sed -i apresenta, dependendo das permissões de diretório e arquivo, sed -i pode permitir que um usuário sobrescreva um arquivo que esse usuário não tenha permissão para editar. / p>

Você também pode fazer backups como:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE
    
por 04.09.2014 / 21:16
1

Você pode usar o Vim no modo Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 selecione a primeira linha

  2. i inserir texto e nova linha

  3. x salvar e fechar

por 17.04.2016 / 04:05