Acabei de chegar ao problema de ter que cortar algumas linhas de um arquivo grande (gigabyte) e, estando ciente do potencial consumo de CPU ao tentar lê-lo na memória, queria editá-lo no local ... e então me deparei com estas questões:
- Como removo certas linhas (usando números de linha) em um arquivo?
- Existe uma maneira de modificar um arquivo no local?
... e ainda estes:
No entanto, eu estava pensando em outra coisa: acredito (mas não tenho certeza) que qualquer sistema de arquivos (como ext3
) teria que empregar algo como uma lista vinculada, para poder descrever algo como fragmentos de um arquivo que são mapeado para áreas do disco.
Assim, deveria ser possível fazer algo assim - por exemplo, digamos, eu tenho um arquivo bigfile.dat
como este (os números devem indicar o deslocamento de bytes, mas é um pouco difícil alinhá-los):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
L 1\n L 2\n L 3\n L 4\n L 5\n L 6\n
Este arquivo poderia então, em princípio, ser carregado em um aplicativo de terminal para navegação - vamos imaginar que chamamos um tool editsegments bigfile.dat
, e digamos que seja semelhante a como less -N bigfile.dat
exibiria o mesmo arquivo (com números de linha):
1 1 L 1
2 2 L 2 *
3 3 L 3
4 4 L 4 *
5 5 L 5
6 6 L 6
bigfile.dat (END)
Digamos que eu poderia inserir um comando lá (digamos, d
para excluir linhas), clicar em outra tecla ou no mouse onde está indicado acima *
- o que significa que tudo entre as linhas 2 e 4 deve ser excluído. O programa então responderia com isto sendo mostrado:
1 1 L 1
2 5 L 5
3 6 L 6
bigfile.dat (END)
Agora podemos ver que a primeira coluna mais à esquerda mostra o número da linha "nova" (após o corte), a segunda coluna é o número da linha "antiga" (antes do corte) - e então o conteúdo real da linha segue.
Agora, o que imagino que aconteça depois que esse pseudoaplicativo editsegments
for encerrado, é que, antes de mais nada, bigfile.dat
ele permanece intocado; entretanto, agora haveria também um arquivo de texto extra no mesmo diretório, digamos bigfile.dat.segments
; com estes conteúdos:
d 4:15 # line 2-4
... e além disso, um arquivo especial (como um "link simbólico") - vamos chamá-lo bigfile.dat.iedit
- apareceria.
Agora, basicamente, o resultado de tudo isso seria que, se eu tentar abrir bigfile.dat.iedit
com algo como less -N bigfile.dat.iedit
, gostaria de obter o conteúdo "editado":
1 L 1
2 L 5
3 L 6
bigfile.dat (END)
... o que poderia ser alcançado, eu acho, instruindo de alguma forma o sistema operacional, que quando $FILE.iedit
for aberto, primeiro $FILE.segments
deve ser aberto e lido; o d 4:15
instruiria que os bytes 4 a 15 no arquivo original deveriam ser omitidos - o que resultaria em algo como:
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L 5\n
L 6\n
0 1 2 3 ------------------------------->16 17 18 19 20 21 22 23
Em outras palavras -assumindoque em um conceito de sistema de arquivos de arquivo, cada byte de conteúdo também contém um "link" para o próximo byte na cadeia - deve ser possível instruir o sistema de arquivos a estabelecer uma nova lista vinculada com base em um script e fornecer o conteúdo conforme representado por esta lista vinculada modificada por meio de um arquivo especial (link simbólico ou pipe).
Isso é o que eu quis dizer com "script" no título - que a "nova" lista vinculada pode ser controlada por um arquivo de script ( $FILE.segments
), editável pelo usuário em um editor de texto (ou gerado por um aplicativo front-end). O que quero dizer com “multipass” é o fato de que bigfile.dat
neste processo não há nenhuma modificação; então eu poderia editar o primeiro gigabyte (original) hoje, salvando o progresso em ( $FILE.segments
) - então eu poderia editar o segundo gigabyte amanhã, salvando novamente o progresso em ( $FILE.segments
) etc. - o tempo todo, o original bigfile.dat
permanece inalterado.
Quando todas as edições forem concluídas, provavelmente seria possível chamar uma espécie de comando (digamos, editsegments --finalize bigfile.dat
), que simplesmente codificaria permanentemente a nova lista vinculada como o conteúdo de bigfile.dat
(e, de acordo com isso, remover bigfile.dat.segments
e bigfile.dat.iedit
). Ou ainda mais fácil, pode-se simplesmente fazer:
cp bigfile.dat.iedit /path/to/somewhere/else/bigfile.modified.dat
É claro que, além de um d
comando de script elete, r
também se poderia ter um comando eplace, digamos:
r 16:18 AAA
... dizendo: substitua o conteúdo entre os bytes 16 e 18 pelos próximos 18-16+1=3 bytes após o espaço (ou seja, o AAA
) - a lista vinculada poderia de fato "enganchar" no próprio conteúdo do comando de script ( o gráfico abaixo contendo também o d
elete):
0 1 2 3 4 5 6 7 8 9 10 11 12,3,4 15 16 17 18 19 20 21 22 23
L 1\n
L
2
\n
L
3
\n
L
4
\n
L
5
\n
L 6\n
0 1 2 3 ------------------------------->| | 19 20 21 22 23
.
.
.
.
.
.
.
\n
r
1
6
:
1
8
AAA
\n
.
.
.
.
Agora, acho que programas como hexedit
(como mencionadoaqui) altere os arquivos no local - mas eu gostaria apenas do benefício da possibilidade de script (ainda melhor se pudesse ser regulado por um aplicativo GUI, mesmo que seja um terminal) e o benefício de não ter realmente o arquivo original alterado, até que se confirme que todas as edições são conforme necessário.
Não tenho certeza se algo assim é possível - e mesmo que seja, acho que pode exigir um driver dedicado (em vez de apenas um programa de usuário)... Mas acho que vale a pena perguntar de qualquer maneira - existe algo assim para Linux?
Muito obrigado antecipadamente por qualquer resposta,
Felicidades!
Responder1
A estrutura dos arquivos no disco depende do sistema de arquivos em uso. Nenhum dos sistemas de arquivos do mundo real usa listas vinculadas conforme você descreve (isso tornaria fseek(3)
insuportável). A coisa mais próxima disso é a MicrosoftGORDO, essencialmente movendo os ponteiros dos blocos de dados para uma matriz que os sombreia.
Mas a maioria dos sistemas de arquivos usa algumas referências baseadas em ponteiros para blocos de dados no arquivo, então, em princípio, alguém poderia cortar um bloco de um arquivo apenas embaralhando um punhado de ponteiros (não todo o conteúdo do arquivo) e marcando um bloco no meio do arquivo como gratuito. Infelizmente, essa não é uma operação muito útil, os blocos de arquivo são bastante grandes (normalmente 4KiB) e raramente se alinham razoavelmente com as estruturas do arquivo (sejam linhas ou outras subdivisões).
Responder2
O que você descreve parece muito com umrepetirde um editor de textolista de refazercontra o arquivo original inalterado ao quallista de refazerpertence. Tenho certeza que gvim
tem um talpersistentelista de desfazer/refazer, que você pode (?) ser capaz de utilizar, e eu sei que emacs
definitivamente tem uma lista que você provavelmente poderia persuadir a fazer o que quiser (por meio de um elisp
script), por exemplo.Salvar o histórico de desfazer do Emacs entre sessões.
Como observação lateral, desligar todas as ações indesejadas pode ser uma boa ideia para arquivos tão grandes, por exemplo:salvamento automático,destaque de sintaxe(lento em umgrandearquivo emacs), etc. e o emacs em um sistema de 32 bits tem 256 MBlimite de tamanho de arquivo.
Certamente não será tão conciso quanto o que você sugeriu, mas pode ser útil se não houver um grande número de alterações.
Responder3
Geralmente, você não pode editar um arquivo sem colocar o arquivo inteiro na memória. Presumo que o que você realmente deseja fazer é apenas ter um novo arquivo que seja uma cópia do antigo, sem linhas específicas. Isso pode ser feito facilmente usando os utilitários unix head
e tail
. Por exemplo, para copiar tudo, exceto as linhas 5, 12 e 52 de um arquivo, você pode fazer
head -n 4 bigfile.dat > tempfile.dat
tail -n +6 bigfile.dat | head -n 6 >> tempfile.dat
tail -n +13 bigfile.dat | head -n 39 >> tempfile.dat
tail -n 53 bigfile.dat >> tempfile.dat
Caso você não esteja familiarizado com esses utilitários, explicarei com mais detalhes.
O head
utilitário imprime as primeiras n linhas de um arquivo. Se não receber um argumento posicional, ele usará a entrada padrão como arquivo. A -n
bandeira informa quantas linhas imprimir. Portanto, head -n 2
imprimirá apenas as 2 primeiras linhas da entrada padrão.
O tail
utilitário imprime as últimas n linhas de um arquivo. Assim como o head, ele pode ler um arquivo ou entrada padrão. O sinalizador -n informa ao tail quantas linhas imprimir a partir do final. Você também pode prefixar o número com um sinal de mais para dizer ao tail para imprimir as linhas do final do arquivo, começando com tantas linhas desde o início. Por exemplo, tail -n 2
imprime as duas últimas linhas da entrada padrão. No entanto, tail -n +2
imprime todas as linhas começando com a linha número 2 (omite a linha 1).
Então, em geral, se você quiser imprimir linhas no intervalo [x, y) de um arquivo, você faria
`tail -n +x | head -n d`
onde d = y - x. Esses comandos produzirão um novo arquivo. Você pode então excluir o arquivo antigo, se desejar. A vantagem de fazer dessa forma é que head
você tail
só precisa manter uma linha na memória por vez, para não encher rapidamente sua RAM.
Responder4
Parece um trabalho para um script sed. IIRC foi projetado para tais tarefas. Processamento linha por linha, processamento repetido do mesmo grupo de comandos e regex, todos combinados em uma ferramenta. Embora eu saiba que isso funcionará, não posso orientá-lo além de direcioná-lo para a multapágina de manual.