Como faço para executar algum comando editando seu arquivo (argumento) "no lugar" usando o bash?
-
02-07-2019 - |
Pergunta
Eu tenho um arquivo temp.txt, que quero classificar com o sort
comando em bash.
Eu quero os resultados classificados para substituir o arquivo original.
Isso não funciona, por exemplo (eu recebo um arquivo vazio):
sortx temp.txt > temp.txt
Isso pode ser feito em uma linha sem recorrer a copiar para arquivos temporários?
Editar: o -o
a opção é muito legal para sort
. eu usei sort
na minha pergunta como exemplo. Eu encontro o mesmo problema com outros comandos:
uniq temp.txt > temp.txt.
Existe uma solução geral melhor?
Solução
sort temp.txt -o temp.txt
Outras dicas
UMA sort
Precisa ver toda a entrada antes de começar a sair. Por esse motivo, o sort
O programa pode oferecer facilmente uma opção para modificar um arquivo no local:
sort temp.txt -o temp.txt
Especificamente, o Documentação de GNU sort
diz:
Normalmente, Sort lê todas as entradas antes de abrir o arquivo de saída, para que você possa classificar com segurança um arquivo no lugar usando comandos como
sort -o F F
ecat F | sort -o F
. No entanto,sort
com--merge
(-m
) pode abrir o arquivo de saída antes de ler todas as entradas, então um comando comocat F | sort -m -o F - G
não é seguro, pois o tipo pode começar a escreverF
antes dacat
acabou de ler.
Enquanto a documentação do BSD sort
diz:
Se [o] arquivo de saída for um dos arquivos de entrada, classifique-o para um arquivo temporário antes de classificar e gravar a saída para [o] arquivo de saída.
Comandos como uniq
pode começar a escrever a saída antes que eles terminem de ler a entrada. Esses comandos normalmente não suportam a edição no local (e seria mais difícil para eles apoiarem esse recurso).
Você normalmente contornou isso com um arquivo temporário ou, se deseja evitar um arquivo intermediário, pode usar um buffer para armazenar o resultado completo antes de escrevê -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 de uniq
em variável $_
e depois substitui o arquivo original com esses dados. Você pode fazer o mesmo na linguagem de script de sua escolha, talvez até em Bash. Mas observe que ele precisará de memória suficiente para armazenar o arquivo inteiro, isso não é aconselhável ao trabalhar com arquivos grandes.
Aqui está uma abordagem mais geral, trabalha com a Uniq, classificação e outros enfeites.
{ rm file && uniq > file; } < file
Comentário de Tobu sobre esponja Garam sendo uma resposta por si só.
Para citar do MoreUtils pagina inicial:
Provavelmente, a ferramenta de uso mais geral em MoreUtils até agora é a esponja (1), o que permite fazer coisas assim:
% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd
No entanto, sponge
sofre do mesmo problema Steve Jessop comenta aqui. Se algum dos comandos no pipeline antes sponge
Falha, então o arquivo original será escrito.
$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found
Uh-oh, my-important-file
se foi.
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.
eu gosto de sort file -o file
Responda, mas não quero digitar o mesmo nome de arquivo duas vezes.
Usando Bash Expansão de história:
$ sort file -o !#^
Agarra o primeiro arg da linha atual quando você pressiona digitar.
Um tipo único no local:
$ sort -u -o file !#$
Agarra o último arg na linha atual.
Muitos mencionaram o -o opção. Aqui está a parte da página do homem.
Da página do homem:
-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.
Isso seria altamente restrito à memória, mas você poderia usar o AWK para armazenar os dados intermediários na memória e, em seguida, escrevê -los de volta.
uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
Uma alternativa a sponge
com o mais comum sed
:
sed -ni r<(command file) file
Funciona para qualquer comando (sort
, uniq
, tac
, ...) e usa o muito conhecido sed
's -i
opção (Editar arquivos no local).
Aviso: Tentar command file
Primeiro porque a edição de arquivos no local não é segura por natureza.
Explicação
Em primeiro lugar, você está dizendo sed
não imprimir as linhas (originais) (-n
opção), e com a ajuda do sed
's r
comando e bash
's Substituição do processo, o conteúdo gerado por <(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
Use o argumento --output=
ou -o
Acabei de tentar no FreeBSD:
sort temp.txt -otemp.txt
Para adicionar o uniq
Capacidade, quais são as desvantagens de:
sort inputfile | uniq | sort -o inputfile
Leia o editor não interativo, ex
.
Se você insistir em usar o sort
programa, você tem que usar um arquivo intermediário - eu não acho sort
tem uma opção para classificar na memória. Qualquer outro truque com o stdin/stdout falhará, a menos que você possa garantir que o tamanho do buffer para o STDIN da Sort seja grande o suficiente para ajustar o arquivo inteiro.
EDIT: Que vergonha. sort temp.txt -o temp.txt
Funciona excelente.
Outra solução:
uniq file 1<> file