Como executo qualquer comando editando seu arquivo (argumento) “no local” usando o bash?


110

Eu tenho um arquivo temp.txt, que desejo classificar com o sortcomando em bash.

Quero que os resultados classificados substituam o arquivo original.

Isso não funciona, por exemplo (recebo um arquivo vazio):

sortx temp.txt > temp.txt

Isso pode ser feito em uma linha sem recorrer à cópia para arquivos temporários?


EDIT: A -oopção é muito legal para sort. Usei sortna minha pergunta como exemplo. Eu tenho o mesmo problema com outros comandos:

uniq temp.txt > temp.txt.

Existe uma solução geral melhor?


Respostas:


171
sort temp.txt -o temp.txt

3
Esta é uma resposta. Na verdade, eu queria saber se existe uma solução genérica para esse problema. Por exemplo, se eu quiser encontrar todas as linhas UNIQ em um arquivo "no lugar", não posso fazer -o
jm.

Não é genérico, mas você pode usar -u com GNU sort para encontrar linhas exclusivas
James,

Alguém resolveu o problema para permitir, por exemplo sort --inplace *.txt? Isso seria muito legal
ver

@sehe Tente isto:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan

29

A sortprecisa ver todas as entradas antes de começar a produzir. Por esse motivo, o sortprograma pode facilmente oferecer uma opção para modificar um arquivo no local:

sort temp.txt -o temp.txt

Especificamente, a documentação do GNUsort diz:

Normalmente, o sort lê todas as entradas antes de abrir o arquivo de saída, para que você possa classificar um arquivo com segurança usando comandos como sort -o F Fe cat F | sort -o F. No entanto, sortwith --merge( -m) pode abrir o arquivo de saída antes de ler todas as entradas, portanto, um comando como cat F | sort -m -o F - Gnão é seguro, pois sort pode começar a escrever Fantes de catterminar de lê-lo.

Enquanto a documentação do BSD sortdiz:

Se [o] arquivo de saída for um dos arquivos de entrada, sort o copia para um arquivo temporário antes de classificar e gravar a saída em [o] arquivo de saída.

Comandos como uniqpodem começar a gravar a saída antes de terminarem de ler a entrada. Normalmente, esses comandos não oferecem suporte à edição local (e seria mais difícil para eles oferecer suporte a esse recurso).

Normalmente, você contorna isso com um arquivo temporário ou, se deseja absolutamente evitar um arquivo intermediário, pode usar um buffer para armazenar o resultado completo antes de gravá-lo. Por exemplo, com perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Aqui, a parte perl lê a saída completa da uniqvariável $_e sobrescreve o arquivo original com esses dados. Você pode fazer o mesmo na linguagem de script de sua escolha, talvez até mesmo no Bash. Mas observe que será necessária memória suficiente para armazenar o arquivo inteiro, isso não é aconselhável ao trabalhar com arquivos grandes.


19

Aqui está uma abordagem mais geral, funciona com uniq, sort e outros enfeites.

{ rm file && uniq > file; } < file

14
Outra abordagem genérica, com spongedos moreutils: cat file |frobnicate |sponge file.
Tobu

3
@Tobu: por que não enviar isso como uma resposta separada?
Flimm

1
Provavelmente, é bom notar que isso não preserva necessariamente as permissões dos arquivos. Seu umask dita quais serão as novas permissões.
wor

1
Complicado. Você pode explicar como isso funciona exatamente?
patryk.beza

2
@ patryk.beza: Em ordem: O FD de entrada é aberto a partir do arquivo original; a entrada original do diretório é excluída; o redirecionamento é processado, criando um novo arquivo vazio com o mesmo nome que o antigo tinha; então o comando é executado.
Charles Duffy,

10

O comentário de Tobu sobre a esponja justifica ser uma resposta por si só.

Para citar a página inicial do moreutils :

Provavelmente, a ferramenta de uso mais geral em moreutils até agora é a esponja (1), que permite fazer coisas como esta:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

No entanto, spongesofre do mesmo problema que Steve Jessop comenta aqui. Se qualquer um dos comandos do pipeline spongefalhar antes , o arquivo original será sobrescrito.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Uh-oh, my-important-filesumiu.


1
O Sponge sabe que será usado para substituir o arquivo de entrada e inicialmente cria um arquivo temporário para evitar uma condição de corrida. Para que isso funcione, a esponja deve ser o último elemento no pipeline e deve ter permissão para criar o próprio arquivo de saída (em oposição ao redirecionamento de saída no nível do shell, por exemplo). BTW: Parece que uma solução fácil de código-fonte para o caso de 'falha' seria não renomear o arquivo temporário no caso de um pipefail (não sei por que o sponge não tem essa opção).
Brent Bradburn

Acho que se você adicionar set -o pipefailno início do seu script, o erro em mistyped_command my-important-filefaria o script sair imediatamente, antes de ser executado sponge, preservando assim o arquivo importante.
Elouan Keryell-Even

6

Aqui está, uma linha:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Tecnicamente, não há cópia para um arquivo temporário e o comando 'mv' deve ser instantâneo.


6
Hm. Eu ainda chamaria temp.txt.sort um arquivo temporário.
JesperE

5
Este código é arriscado, porque se a classificação falhar por qualquer motivo sem concluir seu trabalho, o original será sobrescrito.
Steve Jessop

1
A falta de espaço em disco é uma causa plausível ou um sinal (o usuário pressiona CTRL-C).
Steve Jessop

5
se você quiser usar algo assim, use && (lógico e) em vez de; porque usar isso garantirá que, se um comando falhar, o próximo não será executado. por exemplo: cp backup.tar /root/backup.tar && rm backup.tar se você não tiver direitos para copiar, estará seguro, pois o arquivo não será excluído
daniels

1
mudei minha resposta para levar em consideração suas sugestões, obrigado
davr

4

Gosto da sort file -o fileresposta, mas não quero digitar o mesmo nome de arquivo duas vezes.

Usando a expansão da história do BASH :

$ sort file -o !#^

pega o primeiro argumento da linha atual quando você pressiona enter.

Uma classificação única no local:

$ sort -u -o file !#$

pega o último argumento na linha atual.


3

Muitos mencionaram o -o opção . Aqui está a parte da página do manual.

Na página de manual:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.

3

Isso seria altamente limitado pela memória, mas você poderia usar o awk para armazenar os dados intermediários na memória e, em seguida, gravá-los novamente.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt

Eu acho que é possível os >trunca o arquivo antes do comando ( uniqneste caso) lê-lo.
Martin

3

Uma alternativa ao spongemais comum sed:

sed -ni r<(command file) file

Ele funciona para qualquer comando ( sort, uniq, tac, ...) e usa o bem conhecido sed's -iopção (editar arquivos no local).

Aviso: tente command fileprimeiro porque editar arquivos no local não é seguro por natureza.


Explicação

Em primeiro lugar, você está dizendo sednão para imprimir a linhas (original) ( -nopção ), e com a ajuda do sed's rcomando e bashdo processo de substituição , o conteúdo gerado pelo <(command file)será a saída salva no lugar .


Tornando as coisas ainda mais fáceis

Você pode envolver esta solução em uma função:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Exemplo

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file

1

Use o argumento --output=ou-o

Tentei no FreeBSD:

sort temp.txt -otemp.txt

Embora correto, é simplesmente uma duplicata desta resposta
uau

1

Para adicionar a uniqcapacidade, quais são as desvantagens de:

sort inputfile | uniq | sort -o inputfile


0

Se você insiste em usar o sortprograma, tem que usar um arquivo intermediário - não acho que sorttenha uma opção de ordenação na memória. Qualquer outro truque com stdin / stdout falhará, a menos que você possa garantir que o tamanho do buffer para stdin do sort é grande o suficiente para caber no arquivo inteiro.

Editar: vergonha para mim. sort temp.txt -o temp.txtfunciona excelente.


Eu li o Q também como estando "no lugar", mas a segunda leitura me fez acreditar que ele não estava realmente pedindo por isso
epatel

0

Outra solução:

uniq file 1<> file

Deve-se notar, entretanto, que o <>truque só funciona neste caso porque uniqé especial porque ele apenas copia as linhas de entrada para linhas de saída, deixando algumas no caminho. Se outro comando (por exemplo sed) foi usado que mudaria a entrada (por exemplo, mudaria todos aem aa), então ele pode substituir filede maneiras que não fazem nenhum sentido e até mesmo fazer um loop infinito, desde que a entrada seja suficientemente grande (mais de um buffer de leitura único).
David
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.