Esta é provavelmente uma solução complexa .
Estou à procura de um operador simples como ">>", mas de prefixo.
Receio que não exista. Vou ter que fazer algo como
mv myfile tmp cat myheader tmp> myfile
Algo mais inteligente?
Esta é provavelmente uma solução complexa .
Estou à procura de um operador simples como ">>", mas de prefixo.
Receio que não exista. Vou ter que fazer algo como
mv myfile tmp cat myheader tmp> myfile
Algo mais inteligente?
Respostas:
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 ( ) >&3
parece 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
Isso ainda usa um arquivo temporário, mas pelo menos está em uma linha:
echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile
-
depois cat
?
echo -n "text"
yourfile
for um link simbólico, isso não fará o que você deseja.
echo '0a
your text here
.
w' | ed some_file
ed é o editor padrão! http://www.gnu.org/fun/jokes/ed.msg.html
0r header.file
echo -e '0a\nyour text here\n.\nw' | ed some_file
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.
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.
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; } )
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
{ echo "prepended text"; cat 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 gato 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 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'.
this is the header
em my.txt. Mesmo depois de atualizar o Bash para 4.3.42(1)-release
obter o mesmo resultado.
<(...)
do processo ( ) grava, então não há garantia de que my.txt
seja lido na íntegra , sem o qual essa técnica não funcionará.
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 sponge
você pode instalar (você instala cat blah.txt | grep something | sponge blah.txt
e 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?).
Como Daniel Velkov sugere, use tee.
Para mim, essa é uma solução inteligente simples:
{ echo foo; cat bar; } | tee bar > /dev/null
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
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.
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.
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
Aqui está o que eu descobri:
echo -e "header \n$(cat file)" >file
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 vis
e cat
pode 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.
sed -i -e "1s/^/new first line\n/" old_file.txt
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.
echo
parece ser uma opção mais segura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
${new_line}
, não o arquivo de destino
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.
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-msg
um .gitmessage
arquivo 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 1r
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 em cima do seu .gitmessage
, pois você terminará com duas linhas vazias. -s
suprime 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'
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
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.
cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile
, no entanto, isso é suficientemente diferente da minha resposta para que seja sua própria resposta.
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 myheader
e 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 myfile
valor atual do ponteiro do sistema de arquivos para myheader
e substitua no sistema EOF
de arquivos o de myheader
por 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 .
mktemp
? Você sempre pode limpar o arquivo temporário depois ...