flag sed in-place que funciona tanto em Mac (BSD) quanto em Linux

237

Existe uma chamada sedpara edição in-loco de tarefas sem backups que funciona tanto no Linux quanto no Mac? Embora o BSD sedenviado com o OS X pareça necessário sed -i '' …, as seddistribuições GNU Linux geralmente vêm com interpretam as aspas como um nome de arquivo de entrada vazio (em vez da extensão de backup) e precisam sed -i ….

Existe alguma sintaxe da linha de comando que funcione com os dois tipos, para que eu possa usar o mesmo script nos dois sistemas?

dnadlinger
fonte
Perl não é uma opção? Você deve usar sed?
Noufal Ibrahim 17/04
Talvez instale a versão GNU e use-a! topbug.net/blog/2013/04/14/… ? Eu tentaria isso primeiro. Então, novamente, esse tipo de porcaria também.
Dmitry Minkovsky
2
@dimadima: Pode ser interessante para outras pessoas que navegam nesta pergunta que possuem scripts pessoais que quebram em sua máquina OS X. No entanto, no meu caso, eu precisava dele para o sistema de compilação de um projeto de código aberto, onde dizer ao seu usuário para instalar o GNU sed primeiro teria derrotado o objetivo original deste exercício (corrija alguns arquivos da maneira "funciona em todos os lugares") .
dnadlinger 18/09/14
@klickverbot sim, faz sentido. Eu adicionei o comentário pela primeira vez como resposta e, em seguida, excluí-o, percebendo que não era uma resposta para sua pergunta :).
Dmitry Minkovsky
1
Referência cruzada: como obter portabilidade com o sed -i (edição no local)? no Unix e Linux Stack Exchange.
MvG 10/01

Respostas:

212

Se você realmente deseja apenas usar sed -ia maneira 'fácil', o seguinte funciona no GNU e no BSD / Mac sed:

sed -i.bak 's/foo/bar/' filename

Observe a falta de espaço e o ponto.

Prova:

# GNU sed
% sed --version | head -1
GNU sed version 4.2.1
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

# BSD sed
% sed --version 2>&1 | head -1
sed: illegal option -- -
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file  file.bak
% cat ./file
bar

Obviamente, você pode excluir os .bakarquivos.

vacilar
fonte
2
Este é o caminho a percorrer. Adicione alguns caracteres e é portátil. Excluindo o arquivo de backup é trivial (você já tem o nome do arquivo ao invocar sed)
slezica
Isso funcionou no mac, mas no ubuntu 16.04 ele substituiu o arquivo original em 0 bytes e cria o arquivo .bak também com 0 bytes?
house9
1
É importante notar que no macOS Sierra (e possivelmente antes, não tenho uma máquina à mão), sednão aceita um commandargumento posicional quando invocado com -i- ele deve ser fornecido com outro sinalizador -e,. Assim, o comando se torna:sed -i.bak -e 's/foo/bar/' filename
ELLIOTTCABLE
5
Isso cria backups, enquanto o OP solicita especificamente a edição no local.
Marc-André Lafortune
8
Portanto, a solução completa é:sed -i.bak 's/foo/bar/' file && rm file.bak
joeytwiddle
112

Isso funciona com o GNU sed, mas não no OS X:

sed -i -e 's/foo/bar/' target.file
sed -i'' -e 's/foo/bar/' target.file

Isso funciona no OS X, mas não no GNU sed:

sed -i '' -e 's/foo/bar/' target.file

No OS X você

  • não pode ser usado, sed -i -epois a extensão do arquivo de backup seria definida como-e
  • não pode ser usado sed -i'' -epelos mesmos motivos - ele precisa de um espaço entre -ie ''.
dnadlinger
fonte
1
@AlexanderMills Diz que o próximo argumento é um comando - também pode ser pulado aqui.
Benjamin W.
4
Observe que -ie -i''são idênticos no momento da análise de shell; sed não pode se comportar de maneira diferente nessas duas primeiras invocações, porque obtém exatamente os mesmos argumentos.
wchargin
44

Quando no OSX, eu sempre instalo a versão GNU sed via Homebrew, para evitar problemas nos scripts, porque a maioria dos scripts foi escrita para as versões GNU sed.

brew install gnu-sed --with-default-names

Então o seu BSD sed será substituído pelo GNU sed.

Como alternativa, você pode instalar sem nomes padrão, mas depois:

  • Mude o seu PATHconforme as instruções após a instalaçãognu-sed
  • Verifique seus scripts para escolher entre gsedou seddependendo do seu sistema
Lewy
fonte
4
o --with-default-namesfoi removido do homebrew-core , mais informações em esta resposta. ao instalar gnu-sedagora, as instruções de instalação especificam que você precisa adicionar gnubinao seu PATH:PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
pgericson
18

Como Noufal Ibrahim pergunta, por que você não pode usar Perl? Qualquer Mac terá Perl, e há muito poucas distribuições Linux ou BSD que não incluem alguma versão do Perl no sistema básico. Um dos únicos ambientes que podem realmente não ter o Perl seria o BusyBox (que funciona como o GNU / Linux para -i, exceto que nenhuma extensão de backup pode ser especificada).

Como o ismail recomenda,

Como o perl está disponível em todos os lugares, apenas o faço perl -pi -e s,foo,bar,g target.file

e isso parece ser uma solução melhor em quase todos os casos do que scripts, aliases ou outras soluções alternativas para lidar com a incompatibilidade fundamental sed -ientre o GNU / Linux e o BSD / Mac.

Alex Dupuy
fonte
Eu amo essa solução. perlfaz parte da especificação Linux Standard Base desde 1 de maio de 2009. refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Languages/…
OregonTrail
1
Isso é facilmente a melhor solução para a portabilidade
ecnepsnai
17

Não há como fazê-lo funcionar.

Uma maneira é usar um arquivo temporário como:

TMP_FILE=`mktemp /tmp/config.XXXXXXXXXX`
sed -e "s/abc/def/" some/file > $TMP_FILE
mv $TMP_FILE some/file

Isso funciona em ambos

análogo
fonte
Existe uma razão para que SOME_FILE = "$ (sed -s" s / abc / def / "some / file)"; echo "$ SOME_FILE"> algum / arquivo; em vez disso, não funciona
Jason Gross
Parece que você ficaria limitado pelo tamanho máximo de uma variável do bash, não? Não tenho certeza se funcionará com arquivos de GBs.
Análogo
3
Se você está criando um arquivo temporário, por que não dar uma extensão para criar um arquivo de backup (que você pode remover) como o @kine sugere abaixo?
AlexDupuy
13

Resposta: Não.

A resposta originalmente aceita, na verdade, não faz o que é solicitado (conforme observado nos comentários). (Encontrei esta resposta ao procurar o motivo pelo qual um file-eestava aparecendo "aleatoriamente" nos meus diretórios.)

Aparentemente, não há como começar sed -ia trabalhar consistentemente no MacOS e no Linuces.

Minha recomendação, para o que vale a pena, não é atualizar no local sed(que possui modos de falha complexos), mas gerar novos arquivos e renomeá-los posteriormente. Em outras palavras: evite -i.

Steve Powell
fonte
9

A -iopção não faz parte do POSIX Sed . Um método mais portátil seria usar o Vim no modo Ex:

ex -sc '%s/alfa/bravo/|x' file
  1. % selecione todas as linhas

  2. s substituir

  3. x salvar e fechar

Steven Penny
fonte
Essa é a resposta correta. Um recurso importante do POSIX é a portabilidade.
Kajukenbo
9

Aqui está outra versão que funciona no Linux e no macOS sem usar evale sem precisar excluir arquivos de backup. Ele usa matrizes Bash para armazenar os sedparâmetros, o que é mais limpo do que usar eval:

# Default case for Linux sed, just use "-i"
sedi=(-i)
case "$(uname)" in
  # For macOS, use two parameters
  Darwin*) sedi=(-i "")
esac

# Expand the parameters in the actual call to "sed"
sed "${sedi[@]}" -e 's/foo/bar/' target.file

Isso não cria um arquivo de backup, nem um arquivo com aspas anexadas.

nwinkler
fonte
2
Essa é a resposta correta. Gostaria de simplificá-lo um pouco, mas a ideia funciona perfeitamente sedi=(-i) && [ "$(uname)" == "Darwin" ] && sedi=(-i '') sed "${sedi[@]}" -e 's/foo/bar/' target.file
Alex Skrypnyk
Coisa boa! Eu prefiro a versão mais longa para facilitar a leitura.
Nwinkler 31/01/19
1
Essa é a melhor maneira de eu ser compatível com sistemas diferentes.
Victor Choy
6

Resposta de Steve Powell é bastante correta, consultando a página MAN para sed no OSX e Linux (Ubuntu 12.04) destaca a compatibilidade dentro do uso sed 'in-loco' nos dois sistemas operacionais.

JFYI, não deve haver espaço entre o -i e as aspas (que denotam uma extensão de arquivo vazia) usando a versão Linux do sed, portanto

Página do Man Linux

#Linux
sed -i"" 

e

Página de manual OSX sed

#OSX (notice the space after the '-i' argument)
sed -i "" 

Eu contornei isso em um script usando um comando alias'd e a saída do nome do SO ' uname ' dentro de um bash 'if'. A tentativa de armazenar cadeias de comando dependentes do SO em variáveis ​​foi um acerto e um erro ao interpretar as aspas. O uso de ' shopt -s expand_aliases ' é necessário para expandir / usar os aliases definidos em seu script. O uso de shopt é tratado aqui .

Big Rich
fonte
1

Se você precisar executar o script sedem um local bashe NÃO desejar que o resultado seja gerado com arquivos .bkp, e você tenha uma maneira de detectar o sistema operacional (por exemplo, usando ostype.sh ), - O seguinte hack com o bashshell interno evaldeve funcionar:

OSTYPE="$(bash ostype.sh)"

cat > myfile.txt <<"EOF"
1111
2222
EOF

if [ "$OSTYPE" == "osx" ]; then
  ISED='-i ""'
else # $OSTYPE == linux64
  ISED='-i""'
fi

eval sed $ISED 's/2222/bbbb/g' myfile.txt
ls 
# GNU and OSX: still only myfile.txt there

cat myfile.txt
# GNU and OSX: both print:
# 1111
# bbbb

# NOTE: 
# if you just use `sed $ISED 's/2222/bbbb/g' myfile.txt` without `eval`,
# then you will get a backup file with quotations in the file name, 
# - that is, `myfile.txt""`
sdaau
fonte
0

Você pode usar esponja. O Sponge é um antigo programa unix, encontrado no pacote moreutils (tanto no ubuntu quanto provavelmente no debian, e no homebrew no mac).

Ele armazena em buffer todo o conteúdo do canal, aguarde até que o canal esteja fechado (provavelmente significa que o arquivo de entrada já está fechado) e substituirá:

Na página do manual :

Sinopse

arquivo sed '...' | grep '...' | arquivo de esponja

Guillermo
fonte
3
Abordagem agradável - infelizmente não ajuda realmente na situação original, pois o motivo para recorrer ao sed era ter algo para usar em um script auxiliar de plataforma cruzada usado em máquinas clientes, onde você não pode depender de nada além do sistema ferramentas sendo instaladas. Pode ser útil para outra pessoa, no entanto.
dnadlinger
0

O seguinte funciona para mim no Linux e OS X:

sed -i' ' <expr> <file>

por exemplo, para um arquivo que fcontémaaabbaaba

sed -i' ' 's/b/c/g' f

produz aaaccaacaem Linux e Mac. Observe que há uma string entre aspas que contém um espaço , sem espaço entre -ia string e a string. Aspas simples ou duplas funcionam.

No Linux, estou usando a bashversão 4.3.11 no Ubuntu 14.04.4 e no Mac versão 3.2.57 no OS X 10.11.4 El Capitan (Darwin 15.4.0).

David G
fonte
1
Uau, fico feliz por não ouvir todos acima dizendo que era impossível e continuei lendo! Isso realmente funciona. Obrigado!
Personman
4
Não funciona para mim no Mac OS ... novo arquivo com espaço anexado ao fim do nome do arquivo é criado
Frédéric
Não trabalho para mim no Mac ou (/ usr / bin / sed) - Agora tenho um arquivo de backup extra com um espaço anexado ao nome do arquivo :-(
paul_h
Tente tirar o espaço das aspas simples, funciona para mim.
DPS
0

Eu encontrei esse problema. A única solução rápida foi substituir o sed in mac pela versão gnu:

brew install gnu-sed
vikrantt
fonte
Isso duplica uma resposta existente com muito mais detalhes a partir de 2015. #
tripleee