Pergunta

Isto é provavelmente uma solução complexa.

Estou procurando um operador simples como ">>", mas para prefixar.

Receio que não exista.vou ter que fazer algo do tipo

 mv myfile tmp
 cat myheader tmp > myfile

Algo mais inteligente?

Foi útil?

Solução

O hackear abaixo estava uma resposta rápida e improvisada que funcionou e recebeu muitos votos positivos.Então, à medida que a pergunta se tornou mais popular e o tempo passou, pessoas indignadas começaram a relatar que ela meio que funcionava, mas coisas estranhas poderiam acontecer, ou simplesmente não funcionava, então foi furiosamente rejeitada por um tempo.Tão divertido.

A solução explora a implementação exata de descritores de arquivo em seu sistema e, como a implementação varia significativamente entre nixes, seu sucesso depende inteiramente do sistema, definitivamente não é portátil e não deve ser usado para nada, mesmo que vagamente importante.

Agora, com tudo isso resolvido, a resposta foi:


Criando outro descritor de arquivo para o arquivo (exec 3<> yourfile) daí escrevendo para isso (>&3) parece superar o dilema de leitura/gravação no mesmo arquivo.Funciona para mim em arquivos de 600K com awk.No entanto, tentar o mesmo truque usando 'cat' falha.

Passando o prefixo como uma variável para awk (-v TEXT="$text") supera o problema de aspas literais que impede 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

Outras dicas

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

echo '0a
your text here
.
w' | ed some_file

ed é o Editor Padrão! http://www.gnu.org/fun/jokes/ed.msg.html

João Mee:não é garantido que seu método funcione e provavelmente falhará se você anexar mais de 4.096 bytes de material (pelo menos é o que acontece com o gnu awk, mas suponho que outras implementações terão restrições semelhantes).Nesse caso, ele não apenas falhará, mas também entrará em um loop infinito onde lerá sua própria saída, fazendo com que o arquivo cresça 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 preencherá o sistema de arquivos)

Além disso, é muito perigoso editar arquivos dessa maneira e é um péssimo conselho, pois se algo acontecer enquanto o arquivo estiver sendo editado (travamento, disco cheio), é quase certo que você ficará com o arquivo em um estado inconsistente.

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.

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

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

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

cat header myfile | sponge myfile

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 cat com redirecionamento seja executado.

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 acrescentar é o cabeçalho

$ cat header
this is the header

Certifique-se de ter uma linha final em branco 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

Pelo que eu sei, isso só funciona em 'bash'.

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

Quanto a acrescentar uma linha a um arquivo, não é possível fazer isso via piping, como quando você faz algo como cat blah.txt | grep something > blah.txt, ele inadvertidamente deixa o arquivo em branco.Existe um pequeno comando utilitário chamado sponge você pode instalar (você faz cat blah.txt | grep something | sponge blah.txt e armazena em buffer o conteúdo do arquivo e, em seguida, grava-o 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 fazer isso via awk ou similar, mas se você precisar usar shell-script, acho que um arquivo temporário é de longe a maneira mais fácil (/ única?).

EDITAR:Isto está quebrado.Ver Comportamento estranho ao anexar um arquivo com cat e tee

A solução alternativa para o problema de substituição é usar tee:

cat header main | tee main > /dev/null

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

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

Aquele que eu uso.Este permite que você especifique a ordem, caracteres extras, etc. da maneira que desejar:

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

P.S:só que não funciona se os arquivos contiverem texto com barra invertida, porque ele é interpretado como caracteres de escape

Principalmente para diversão/shell golf, mas

ex -c '0r myheader|x' myfile

resolverá o problema e não há pipelines ou redirecionamentos.É claro que o vi/ex não é realmente para uso não interativo, então o vi irá piscar brevemente.

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

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

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

# 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 arquivos abertos, conforme sugerido por Jürgen Hötzel em Redirecionar a saída de 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 poderia ser colocado em uma única linha, é claro.

Uma variante da solução do cb0 para "sem arquivo temporário" para preceder o 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.

Observação:Gostei desta solução.No entanto, no meu Mac, o arquivo original foi perdido (pensei que não deveria, mas aconteceu).Isso poderia ser corrigido escrevendo sua solução como:eco "texto para prender" | CAT - FILE_TO_BE_MODIFICADO | gato> tmp_file;mv tmp_file file_to_be_modified

Aqui está o que descobri:

echo -e "header \n$(cat file)" >file
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

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

Deveria haver uma maneira de fazer a abordagem sed de @shixilun funcionar, apesar de suas dúvidas.Deve haver um comando bash para escapar de espaços em branco ao ler um arquivo em uma string substituta sed (por exemplosubstitua caracteres de nova linha por ' '.Comandos de shell vis e cat pode lidar com caracteres não imprimíveis, mas não com espaços em branco, portanto isso não resolverá o problema do OP:

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 por um &, para manter o shell e o sed felizes, como esta resposta SO

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

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

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

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

Se você tiver um arquivo grande (algumas centenas de kilobytes no meu caso) e acesso ao python, isso é muito mais rápido do que cat soluções para tubos:

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

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ê tem que ter certeza de que não há nenhum % em qualquer lugar, incluindo o conteúdo do arquivo de destino, pois isso pode ser interpretado e atrapalhar seus resultados.

Você pode usar a linha de comando perl:

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

Onde -i criará uma substituição em linha do arquivo e -0777 ingerirá o arquivo inteiro e fará ^ corresponderá apenas o 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.

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

onde "my_file" é o arquivo ao qual preceder "my_string".

Estou gostando @fluffe's Ed abordagem o melhor.Afinal, as opções de linha de comando de qualquer ferramenta versus os comandos do editor com script são essencialmente a mesma coisa aqui;não ver a "limpeza" de uma solução de editor com script sendo menor ou algo assim.

Aqui está minha frase anexada a .git/hooks/prepare-commit-msg para preceder um in-repo .gitmessage arquivo para confirmar mensagens:

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

Exemplo .gitmessage:

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

estou fazendo 1r em vez 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 no topo do seu .gitmessage então, você terminará com duas linhas vazias. -s suprime a saída de informações de diagnóstico de ed.

Em relação a passar por isso, eu descoberto que para os vim-buffs também é bom ter:

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

variáveis, certo?

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

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

Se você estiver criando scripts no BASH, na verdade, você pode simplesmente emitir:

cat - yourfile  /tmp/out && mv /tmp/out yourfile

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

IMHO, não existe uma solução shell (e nunca será uma) que funcione de forma consistente e confiável, independentemente do tamanho dos dois arquivos myheader e myfile.A razão é que se você quiser fazer isso sem recorrer a um arquivo temporário (e sem permitir que o shell recorra silenciosamente a um arquivo temporário, por exemplo,através de construções como exec 3<>myfile, tubulação para tee, etc.

A solução "real" que você está procurando precisa mexer no 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 por myfile para o valor atual do ponteiro do sistema de arquivos para myheader e substitua no sistema de arquivos o EOF de myheader com um link encadeado para o endereço do sistema de arquivos atual apontado por myfile.Isso não é trivial e obviamente não pode ser feito por um não-superusuário, e provavelmente também não pelo superusuário...Brinque com inodes, etc.

Você pode mais ou menos fingir isso usando dispositivos de loop.Veja por exemplo este tópico SO.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top