Eu tenho um arquivo bastante grande (35Gb) e gostaria de filtrar esse arquivo in situ (ou seja, não tenho espaço em disco suficiente para outro arquivo), especificamente quero grep e ignorar alguns padrões - existe uma maneira de fazer isso sem usar outro arquivo?
Digamos que eu queira filtrar todas as linhas que contêm, foo:
por exemplo ...
Respostas:
No nível da chamada do sistema, isso deve ser possível. Um programa pode abrir seu arquivo de destino para gravação sem truncá-lo e começar a escrever o que lê do stdin. Ao ler EOF, o arquivo de saída pode ser truncado.
Como você está filtrando linhas da entrada, a posição de gravação do arquivo de saída sempre deve ser menor que a posição de leitura. Isso significa que você não deve corromper sua entrada com a nova saída.
No entanto, encontrar um programa que faça isso é o problema.
dd(1)
tem a opçãoconv=notrunc
que não trunca o arquivo de saída em aberto, mas também não trunca no final, deixando o conteúdo original do arquivo após o conteúdo grep (com um comando comogrep pattern bigfile | dd of=bigfile conv=notrunc
)Como é muito simples do ponto de vista de chamada do sistema, escrevi um pequeno programa e o testei em um pequeno sistema de arquivos de loopback completo (1MiB). Ele fez o que você queria, mas você realmente deseja testar isso com alguns outros arquivos primeiro. Sempre será arriscado sobrescrever um arquivo.
overwrite.c
Você o usaria como:
Estou postando isso principalmente para que outros comentem antes de tentar. Talvez alguém conheça um programa que faça algo semelhante e que seja mais testado.
fonte
grep
não produzirá mais dados do que lê, a posição de gravação deve estar sempre atrás da posição de leitura. Mesmo se você estiver escrevendo na mesma proporção que a leitura, ainda estará ok. Tente rot13 com isso em vez de grep e novamente. md5sum antes e depois e você verá o mesmo.dd
, mas é complicado.Você pode usar
sed
para editar arquivos no local (mas isso cria um arquivo temporário intermediário):Para remover todas as linhas que contêm
foo
:Para manter todas as linhas contendo
foo
:fonte
$HOME
poderá ser gravado, mas/tmp
será somente leitura (por padrão). Por exemplo, se você possui o Ubuntu e inicializou no Console de recuperação, esse é geralmente o caso. Além disso, o operador aqui documentado<<<
também não funcionará lá, pois precisa/tmp
ser r / w porque também gravará um arquivo temporário nele. (cf. esta questão inclui umastrace
saída)Assumirei que seu comando filter é o que chamarei de filtro encolhimento de prefixo , que tem a propriedade de que o byte N na saída nunca é gravado antes de ler pelo menos N bytes de entrada.
grep
tem essa propriedade (desde que apenas filtre e não faça outras coisas, como adicionar números de linha para correspondências). Com esse filtro, você pode substituir a entrada à medida que avança. Obviamente, você deve ter certeza de não cometer nenhum erro, pois a parte substituída no início do arquivo será perdida para sempre.A maioria das ferramentas unix oferece apenas a opção de anexar a um arquivo ou truncá-lo, sem possibilidade de sobrescrevê-lo. A única exceção na caixa de ferramentas padrão é a
dd
que pode ser solicitada a não truncar seu arquivo de saída. Portanto, o plano é filtrar o comandodd conv=notrunc
. Isso não altera o tamanho do arquivo, então também capturamos o tamanho do novo conteúdo e truncamos o arquivo para esse tamanho (novamente comdd
). Observe que esta tarefa é inerentemente não robusta - se ocorrer um erro, você estará por sua conta.Você pode escrever Perl equivalente áspero. Aqui está uma implementação rápida que não tenta ser eficiente. Obviamente, você também pode fazer sua filtragem inicial diretamente nesse idioma.
fonte
Com qualquer casca semelhante a Bourne:
Por alguma razão, parece que as pessoas tendem a esquecer aquele operador de redirecionamento de leitura e gravação padrão de 40 anos de idade¹ .
Abrimos
bigfile
em modo + write leitura e (o que mais importa aqui), sem cortes nostdout
enquantobigfile
está aberto (separadamente) emcat
'sstdin
. Após ogrep
término, e se ele removeu algumas linhas,stdout
agora aponta para algum lugar dentrobigfile
, precisamos nos livrar do que está além desse ponto. Daí operl
comando que trunca o arquivo (truncate STDOUT
) na posição atual (retornada portell STDOUT
).(
cat
é para o GNUgrep
que reclama se stdin e stdout apontam para o mesmo arquivo).¹ Bem, embora
<>
esteja no shell Bourne desde o início no final dos anos setenta, ele foi inicialmente não documentado e não foi implementado adequadamente . Ele não estava na implementação original deash
1989 e, embora seja umsh
operador de redirecionamento POSIX (desde o início dos anos 90, como o POSIXsh
se baseia noksh88
que sempre o possuía), não foi adicionado ao FreeBSDsh
por exemplo até 2000, de maneira portável por 15 anos. velho é provavelmente mais preciso. Observe também que o descritor de arquivo padrão, quando não especificado, está<>
em todos os shells, exceto queksh93
foi alterado de 0 para 1 no ksh93t + em 2010 (quebrando a compatibilidade com versões anteriores e a conformidade com POSIX)fonte
perl -e 'truncate STDOUT, tell STDOUT'
? Funciona para mim sem incluir isso. Alguma maneira de conseguir a mesma coisa sem usar Perl?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
que é uma dica.Embora essa seja uma pergunta antiga, parece-me uma pergunta perene, e uma solução mais geral e clara está disponível do que foi sugerido até agora. Crédito no momento em que o crédito é devido: não tenho certeza de que teria pensado nisso sem considerar a menção de Stéphane Chazelas ao
<>
operador de atualização.Abrir um arquivo para atualização em um shell Bourne é de utilidade limitada. O shell não oferece uma maneira de procurar em um arquivo, nem como definir seu novo tamanho (se menor que o antigo). Mas isso é facilmente remediado, tão facilmente que me surpreende que não esteja entre os utilitários padrão
/usr/bin
.Isso funciona:
Como faz isso (dica de chapéu para Stéphane):
(Estou usando o GNU grep. Talvez algo tenha mudado desde que ele escreveu sua resposta.)
Exceto que você não possui / usr / bin / ftruncate . Para algumas dezenas de linhas de C, você pode ver abaixo. Esse utilitário ftruncate trunca um descritor de arquivo arbitrário para um comprimento arbitrário, padronizando a saída padrão e a posição atual.
O comando acima (1º exemplo)
T
para atualização. Assim como em open (2), abrir o arquivo dessa maneira posiciona o deslocamento atual em 0.T
normalmente e o shell redireciona sua saída para oT
descritor 4.O subshell então sai, fechando o descritor 4. Aqui está ftruncate :
NB, ftruncate (2) não é portável quando usado dessa maneira. Para generalidade absoluta, leia o último byte escrito, reabra o arquivo O_WRONLY, procure, escreva o byte e feche.
Dado que a pergunta tem 5 anos, vou dizer que esta solução não é óbvia. Ele tira vantagem do exec para abrir um novo descritor e o
<>
operador, ambos arcanos. Não consigo pensar em um utilitário padrão que manipule um inode pelo descritor de arquivo. (A sintaxe pode serftruncate >&4
, mas não tenho certeza de que haja uma melhoria.) É consideravelmente menor que a resposta exploratória e competente de camh. É um pouco mais claro que o de Stéphane, na IMO, a menos que você goste mais do Perl do que eu. Espero que alguém ache útil.Uma maneira diferente de fazer a mesma coisa seria uma versão executável do lseek (2) que relata o deslocamento atual; a saída pode ser usada para / usr / bin / truncate , que alguns Linuxi fornecem.
fonte
ed
é provavelmente a escolha certa para editar um arquivo no local:fonte
ed
versões comportar de maneira diferente ..... isto é deman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
não é uma solução gool para editar arquivos de 35 GB, já que o arquivo é lido em um buffer.!
), para que possa ter mais alguns truques interessantes na manga.ed
trunca o arquivo e o reescreve. Portanto, isso não alterará os dados no disco no local, conforme o OP desejar. Além disso, não funcionará se o arquivo for muito grande para ser carregado na memória.Você pode usar uma festança de leitura / gravação descritor de arquivo para abrir o arquivo (para substituí-lo in-situ), em seguida,
sed
etruncate
... mas é claro, não nunca permitir que as alterações sejam maiores do que a quantidade de dados lido até agora .Aqui está o script (usa: bash variable $ BASHPID)
Aqui está a saída do teste
fonte
Eu mapeava o arquivo na memória, fazia tudo no local usando ponteiros char * para a memória sem memória, depois mapeava o arquivo e o trunava.
fonte
Não exatamente no local, mas - isso pode ser útil em circunstâncias semelhantes.
Se o espaço em disco for um problema, comprima o arquivo primeiro (já que é texto, isso causará uma grande redução) e use sed (ou grep, ou o que for) da maneira usual no meio de um pipeline de descompactação / compactação.
fonte
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Para o benefício de qualquer pessoa pesquisando essa questão no Google, a resposta correta é parar de procurar por recursos obscuros do shell que correm o risco de danificar seu arquivo para obter ganhos insignificantes de desempenho e, em vez disso, use alguma variação desse padrão:
Somente na situação extremamente incomum que, por algum motivo, não é viável, você deve considerar seriamente qualquer uma das outras respostas nesta página (embora elas certamente sejam interessantes de ler). Admito que o dilema do OP de não ter espaço em disco para criar um segundo arquivo é exatamente essa situação. Embora, mesmo assim, existam outras opções disponíveis, por exemplo, conforme fornecidas por @Ed Randall e @Basile Starynkevitch.
fonte
echo -e "$(grep pattern bigfile)" >bigfile
fonte
grepped
dados excederem o comprimento permitido pela linha de comando. em seguida, corrompe os dados