Como posso usar um arquivo em um comando e redirect a saída para o mesmo arquivo sem truncá-lo?

Basicamente eu quero tomar como texto de input de um arquivo, remover uma linha desse arquivo e enviar a saída de volta para o mesmo arquivo. Algo nesse sentido, se isso torna tudo mais claro.

grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > file_name 

no entanto, quando faço isso, acabo com um arquivo em branco. Alguma ideia?

Você não pode fazer isso porque o bash processa os redirecionamentos primeiro, depois executa o comando. Então, no momento em que o grep olha o file_name, ele já está vazio. Você pode usar um arquivo temporário embora.

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > tmpfile cat tmpfile > file_name 

assim, considere o uso do mktemp para criar o arquivo tmp, mas note que não é POSIX.

Use esponja para este tipo de tarefas. Sua parte de moreutils.

Tente este comando:

  grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | sponge file_name 

Use sed em vez disso:

 sed -i '/seg[0-9]\{1,\}\.[0-9]\{1\}/d' file_name 

tente este simples

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name 

Seu arquivo não ficará em branco desta vez 🙂 e sua saída também será impressa no seu terminal.

Você não pode usar o operador de redirecionamento ( > ou >> ) para o mesmo arquivo, porque ele tem uma precedência mais alta e criará / truncará o arquivo antes que o comando seja chamado. Para evitar isso, você deve usar ferramentas apropriadas como tee , sponge , sed -i ou qualquer outra ferramenta que possa gravar resultados no arquivo (por exemplo, sort file -o file ).

Basicamente redirecionando a input para o mesmo arquivo original não faz sentido e você deve usar editores apropriados para isso, por exemplo, Ex editor (parte do Vim):

 ex '+g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' -scwq file_name 

Onde:

  • '+cmd' / -c – executa qualquer comando Ex / Vim
  • g/pattern/d – remove as linhas que correspondem a um padrão usando global ( help :g )
  • -s – modo silencioso ( man ex )
  • -c wq – execute :write e :quit comandos

Você pode usar sed para conseguir o mesmo (como já mostrado em outras respostas), no entanto in-place ( -i ) é extensão não padronizada do FreeBSD (pode funcionar de forma diferente entre Unix / Linux) e basicamente é uma forma de processamento, não um editor de arquivos. Veja: O modo Ex tem algum uso prático?

One liner alternative – define o conteúdo do arquivo como variável:

 VAR=`cat file_name`; echo "$VAR"|grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' > file_name 

Há também ed (como alternativa ao sed -i ):

 # cf. http://wiki.bash-hackers.org/howto/edit-ed printf '%s\n' H 'g/seg[0-9]\{1,\}\.[0-9]\{1\}/d' wq | ed -s file_name 

Você pode fazer isso usando a substituição do processo .

É um pouco um truque, já que o bash abre todos os canais de forma assíncrona e temos que contornar isso usando o sleep para YMMV.

No seu exemplo:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name > >(sleep 1 && cat > file_name) 
  • >(sleep 1 && cat > file_name) cria um arquivo temporário que recebe a saída do grep
  • sleep 1 atrasos por um segundo para dar tempo grep para analisar o arquivo de input
  • finalmente cat > file_name escreve a saída

Você pode usar o slurp com o POSIX Awk:

 !/seg[0-9]\{1,\}\.[0-9]\{1\}/ { q = q ? q RS $0 : $0 } END { print q > ARGV[1] } 

Exemplo

Eu costumo usar o programa tee para fazer isso:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | tee file_name 

Cria e remove um arquivo temporário por si só.

Tente isso

 echo -e "AAA\nBBB\nCCC" > testfile cat testfile AAA BBB CCC echo "$(grep -v 'AAA' testfile)" > testfile cat testfile BBB CCC 

talvez você possa fazer assim:

 grep -v 'seg[0-9]\{1,\}\.[0-9]\{1\}' file_name | cat > file_name