Shell de um liner para anexar previamente a um arquivo


Respostas:


29

O truque abaixo foi uma resposta rápida e rápida que funcionou e recebeu muitos votos positivos. Então, à medida que a pergunta se tornou mais popular e com o passar do tempo, pessoas ultrajadas começaram a relatar que meio que funcionava, mas coisas estranhas podiam acontecer, ou simplesmente não funcionavam, por isso foi furiosamente rejeitada por um tempo. Que divertido.

A solução explora a implementação exata dos descritores de arquivo no seu sistema e, como a implementação varia significativamente entre os nixes, seu sucesso depende totalmente do sistema, definitivamente não é portátil e não deve ser considerado algo vagamente importante.

Agora, com tudo isso fora do caminho, a resposta foi:


A criação de outro descritor de arquivo para o arquivo ( exec 3<> yourfile), portanto, a gravação nesse arquivo ( ) >&3parece superar a leitura / gravação no mesmo dilema de arquivo. Funciona para mim em arquivos de 600K com awk. No entanto, tentar o mesmo truque usando 'gato' falha.

Passar o prefácio como uma variável para awk ( -v TEXT="$text") supera o problema de aspas literais, o que evita fazer esse truque com 'sed'.

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3

3
AVISO: verifique a saída para garantir que nada mudou, exceto a primeira linha. Usei essa solução e, embora parecesse funcionar, notei que algumas novas linhas pareciam ser adicionadas. Não entendo de onde eles vieram, então não posso ter certeza de que esta solução realmente tenha um problema, mas tenha cuidado.
conradlee

1
Observe no exemplo do bash acima que as aspas duplas se estendem sobre o retorno de carro para demonstrar a adição de várias linhas. Verifique se o seu shell e editor de texto estão sendo cooperativos. \ r \ n vem à mente.
John Mee

4
Use isso com cuidado! Veja o seguinte comentário sobre o motivo: stackoverflow.com/questions/54365/…
Alex

3
Se agora não é confiável, que tal atualizar sua resposta com uma solução melhor?
Zakdances 25/10/2013

Um hack é útil se, e somente se , for conhecido exatamente por que ele funciona e, portanto, sob quais restrições cuidadosamente observadas . Apesar de seu aviso, seu hack não atende a esses critérios ("totalmente dependente do sistema", "parece superar", "funciona para mim"). Se levarmos a sério o seu aviso, ninguém deve usar o seu hack - e eu concordo. Então, o que nos resta? Uma distração barulhenta. Vejo duas opções: (a) exclua sua resposta ou (b) transforme-a em um conto de advertência que explica por que - por mais tentador que seja - essa abordagem não pode funcionar em geral .
precisa

102

Isso ainda usa um arquivo temporário, mas pelo menos está em uma linha:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

Crédito: BASH: anexar um texto / linhas a um arquivo


4
O que está fazendo -depois cat?
precisa saber é

Existe uma maneira de adicionar "texto" sem uma nova linha depois?
Chishaku # 28/15

@chishakuecho -n "text"
Sparhawk

3
Um aviso: se yourfilefor um link simbólico, isso não fará o que você deseja.
dshepherd

A resposta é diferente para isso: echo "text"> / tmp / out; arquivo de gato >> / tmp / out; mv / tmp / seu arquivo?
Richard


20

John Mee: não é garantido que seu método funcione e provavelmente falhará se você acrescentar mais de 4096 bytes de material (pelo menos é o que acontece com o gnu awk, mas suponho que outras implementações tenham restrições semelhantes). Não apenas falhará nesse caso, como também entrará em um loop infinito, onde lerá sua própria saída, aumentando o arquivo até que todo o espaço disponível seja preenchido.

Experimente você mesmo:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(aviso: mate-o depois de um tempo ou ele irá preencher o sistema de arquivos)

Além disso, é muito perigoso editar arquivos dessa maneira, e é um péssimo conselho, como se algo acontecesse enquanto o arquivo estava sendo editado (falha, disco cheio). Você quase garante que o arquivo fique em um estado inconsistente.


6
Provavelmente deve ser um comentário, não uma resposta separada.
lkraav

10
@Ikraav: talvez, mas o "comentário" seja muito longo e elaborado (e útil), não parecerá bom e talvez não se encaixe na capacidade limitada de comentários do StackOverflow.
Hendy Irawan

18

Não é possível sem um arquivo temporário, mas aqui vai um oneliner

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

Você pode usar outras ferramentas, como ed ou perl, para fazer isso sem arquivos temporários.


16

Vale a pena notar que muitas vezes é um boa ideia gerar o arquivo temporário com segurança usando um utilitário como mktemp , pelo menos se o script for executado com privilégios de root. Você pode, por exemplo, fazer o seguinte (novamente no bash):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )

15

Se você precisar disso nos computadores que você controla, instale o pacote "moreutils" e use "sponge". Então você pode fazer:

cat header myfile | sponge myfile

Isso funcionou muito bem para mim quando combinado com a resposta de @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

Usando um heredoc bash, você pode evitar a necessidade de um arquivo tmp:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

Isso funciona porque $ (cat myfile) é avaliado quando o script bash é avaliado, antes que o gato com redirecionamento seja executado.


9

assumindo que o arquivo que você deseja editar seja my.txt

$cat my.txt    
this is the regular file

E o arquivo que você deseja anexar é o cabeçalho

$ cat header
this is the header

Certifique-se de ter uma linha em branco final no arquivo de cabeçalho.
Agora você pode anexá-lo com

$cat header <(cat my.txt) > my.txt

Você acaba com

$ cat my.txt
this is the header
this is the regular file

Até onde eu sei, isso só funciona no 'bash'.


por que isso funciona? Os parênteses fazem com que o gato do meio seja executado em um shell separado e despeja o arquivo inteiro de uma só vez?
Catskul 26/10/10

Eu acho que o <(cat my.txt) cria um arquivo temporário em algum lugar do sistema de arquivos. Este arquivo é então lido antes da modificação do arquivo original.
Cb0

Alguém uma vez me mostrou o que você pode fazer com a sintaxe "<()". No entanto, nunca aprendi como esse método é chamado, nem encontrei algo na página de manual do bash. Se alguém souber mais sobre isso, por favor me avise.
CB0

1
Não funcionou para mim. Não sei porque. Estou usando o GNU bash, versão 3.2.57 (1) -release (x86_64-apple-darwin14), estou no OS X Yosemite. Acabei com duas linhas de this is the headerem my.txt. Mesmo depois de atualizar o Bash para 4.3.42(1)-releaseobter o mesmo resultado.
Kohányi Róbert 19/11/2015

1
O Bash não cria um arquivo temporário, ele cria um FIFO (canal) no qual o comando na substituição<(...) do processo ( ) grava, então não há garantia de que my.txtseja lido na íntegra , sem o qual essa técnica não funcionará.
mklement0

9

Quando você começa a tentar fazer coisas que se tornam difíceis no shell-script, sugiro enfaticamente reescrever o script em uma linguagem de script "adequada" (Python / Perl / Ruby / etc)

Quanto à adição de uma linha a um arquivo, não é possível fazer isso por meio de canalização, pois quando você faz algo assim cat blah.txt | grep something > blah.txt, inadvertidamente, o arquivo é apagado. Existe um pequeno comando utilitário chamado spongevocê pode instalar (você instala cat blah.txt | grep something | sponge blah.txte ele armazena em buffer o conteúdo do arquivo, depois o grava no arquivo). É semelhante a um arquivo temporário, mas você não precisa fazer isso explicitamente. mas eu diria que é um requisito "pior" do que, digamos, Perl.

Pode haver uma maneira de fazê-lo via awk, ou similar, mas se você precisar usar o shell-script, acho que um arquivo temporário é de longe a maneira mais fácil (/ somente?).


7

Como Daniel Velkov sugere, use tee.
Para mim, essa é uma solução inteligente simples:

{ echo foo; cat bar; } | tee bar > /dev/null

7

EDIT: Isso está quebrado. Consulte Comportamento estranho ao anexar um arquivo com cat e tee

A solução alternativa para o problema de substituição está usando tee:

cat header main | tee main > /dev/null

Pendurado por um tempo. Terminou com "tee: main: não há espaço no dispositivo". O principal era vários GBs, embora os dois arquivos de texto originais tivessem apenas alguns KB.
Marcos

3
Se a solução que você postou estiver quebrada (e de fato preenche os discos rígidos dos usuários), faça um favor à comunidade e exclua sua resposta.
Aioobe # 26/14

1
Você pode me indicar uma orientação que diz que respostas erradas devem ser excluídas?
Daniel Velkov 26/09

3

O que eu uso. Este permite especificar pedidos, caracteres extras, etc. da maneira que você gosta:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS: só não está funcionando se os arquivos contiverem texto com barra invertida, pois será interpretado como caracteres de escape


3

Principalmente por diversão / golfe, mas

ex -c '0r myheader|x' myfile

fará o truque e não há pipelines ou redirecionamentos. Obviamente, o vi / ex não é realmente para uso não interativo; portanto, o vi piscará brevemente.


Melhor receita do meu ponto de vista, pois usa um comando histórico e faz isso de maneira direta.
24516 Diego

2

Por que não simplesmente usar o comando ed (como já sugerido pelo fluffle aqui)?

ed lê o arquivo inteiro na memória e executa automaticamente uma edição no local!

Então, se seu arquivo não for tão grande ...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

Outra solução alternativa seria usar identificadores de arquivo aberto, conforme sugerido por Jürgen Hötzel na saída Redirect from sed 's / c / d /' myFile para myFile

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

Tudo isso pode ser colocado em uma única linha, é claro.


2

Uma variante da solução da cb0 para "no file temp" para anexar um texto fixo:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

Novamente, isso depende da execução do sub-shell - o (..) - para evitar que o gato se recuse a ter o mesmo arquivo para entrada e saída.

Nota: Gostei desta solução. No entanto, no meu Mac, o arquivo original é perdido (pensei que não deveria, mas existe). Isso pode ser corrigido escrevendo sua solução como: echo "text to prefpend" | cat - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified


1
Eu também acho o arquivo original perdido. Ubuntu Lucid.
Hedgehog

2

Aqui está o que eu descobri:

echo -e "header \n$(cat file)" >file

1
É o mesmo que a sugestão anterior de @nemisj Não?
Hedgehog

2
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

2

AVISO: isso precisa de um pouco mais de trabalho para atender às necessidades do OP.

Deve haver uma maneira de fazer a abordagem sed do @shixilun funcionar, apesar de suas dúvidas. Deve haver um comando bash para escapar do espaço em branco ao ler um arquivo em uma sequência substituta sed (por exemplo, substituir caracteres de nova linha por '\ n'. Comandos do shell vise catpode lidar com caracteres não imprimíveis, mas não em espaço em branco, para que isso não resolva os OPs problema:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

falha devido às novas linhas brutas no script substituto, que precisam ser anexadas com um caractere de continuação de linha () e talvez seguidas de um &, para manter o shell e sed felizes, como esta resposta SO

sed possui um limite de tamanho de 40 K para comandos de substituição de pesquisa não globais (sem rastreamento / g após o padrão), portanto, provavelmente evitaria os problemas assustadores de saturação de buffer do awk que o anônimo alertou.


2
sed -i -e "1s/^/new first line\n/" old_file.txt

exatamente o que eu estava procurando, mas na verdade não é a resposta certa para a pergunta
masterxilo

2

Com $ (comando), você pode gravar a saída de um comando em uma variável. Então eu fiz isso em três comandos em uma linha e sem arquivo temporário.

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile

1

Se você possui um arquivo grande (algumas centenas de kilobytes no meu caso) e acesso ao python, isso é muito mais rápido que as catsoluções de pipe:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'


1

Uma solução com printf:

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

Você também pode fazer:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

Mas, nesse caso, você deve ter certeza de que não há nenhum %lugar, incluindo o conteúdo do arquivo de destino, pois isso pode ser interpretado e estragar seus resultados.


2
Isso parece explodir (e truncar seu arquivo!) Se você tiver algo no arquivo de destino que printf interprete como uma opção de formatação.
101315 Alan H. Alan

echoparece ser uma opção mais segura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
22415 Alan H.

@AlanH. Eu aviso os perigos na resposta.
user137369

Na verdade, você só alertou sobre porcentagens em ${new_line}, não o arquivo de destino
Alan H.

Você poderia ser mais explícito? É realmente uma grande OMI. "Em qualquer lugar" não deixa isso muito claro.
Alan H.

1

Você pode usar a linha de comando perl:

perl -i -0777 -pe 's/^/my_header/' tmp

Onde -i criará uma substituição embutida do arquivo e -0777 engolirá o arquivo inteiro e fará ^ corresponder apenas ao início. -pe imprimirá todas as linhas

Ou se my_header for um arquivo:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

Onde o / e permitirá uma avaliação do código na substituição.


0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

onde "meu_arquivo" é o arquivo para o qual preceder "minha_string".


0

Estou gostando da abordagem ed da @ fluffle da melhor da . Afinal, as opções de linha de comando de qualquer ferramenta e os comandos do editor com script são essencialmente a mesma coisa aqui; não vendo uma solução de editor com script "limpeza" ser menor ou mais do que isso.

Aqui está o meu one-liner anexado a .git/hooks/prepare-commit-msgum .gitmessagearquivo in-repo para confirmar as mensagens:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

Exemplo .gitmessage:

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

Estou fazendo isso em 1rvez de 0r, porque isso deixará a linha vazia pronta para gravação na parte superior do arquivo do modelo original. Não coloque uma linha vazia em cima do seu .gitmessage, pois você terminará com duas linhas vazias. -ssuprime a saída de informações de diagnóstico de ed.

Em conexão com isso, descobri que para o vim-buffs também é bom ter:

[core]
        editor = vim -c ':normal gg'

0

variáveis, ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list

0

Eu acho que esta é a variação mais limpa de ed:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

Como uma função:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile

0

Se você estiver criando scripts no BASH, na verdade, poderá emitir:

cat - seuarquivo / tmp / out && mv / tmp / seuarquivo

Na verdade, isso está no Exemplo complexo que você postou em sua própria pergunta.


Um editor sugeriu mudar isso para cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, no entanto, isso é suficientemente diferente da minha resposta para que seja sua própria resposta.
Tim Kennedy

0

IMHO não há solução shell (e nunca será uma) que funcionaria de forma consistente e confiável, independentemente do tamanho dos dois arquivos myheadere myfile. O motivo é que, se você quiser fazer isso sem retornar a um arquivo temporário (e sem permitir que o shell ocorra silenciosamente em um arquivo temporário, por exemplo, através de construções como exec 3<>myfile, canalizar paratee etc.)

A solução "real" que você está procurando precisa mexer com o sistema de arquivos e, portanto, não está disponível no espaço do usuário e depende da plataforma: você está pedindo para modificar o ponteiro do sistema de arquivos em uso pelo myfilevalor atual do ponteiro do sistema de arquivos para myheadere substitua no sistema EOFde arquivos o de myheaderpor um link encadeado para o endereço atual do sistema de arquivos apontado pormyfile . Isso não é trivial e, obviamente, não pode ser feito por um não-superusuário, e provavelmente pelo superusuário também ... Jogue com inodes, etc.

Você pode mais ou menos fingir isso usando dispositivos de loop, no entanto. Veja, por exemplo, este segmento SO .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.