Gilles me bateu nisso:
não há "ponteiro que aponte para a primeira linha do arquivo".
A primeira linha do arquivo - o começo do arquivo -
é sempre o primeiro caractere do arquivo.
(Pode haver aplicações individuais obscuras
que reconhecem tal noção,
mas não há nada como isso no nível do sistema.)
O que você já sabe:
Comandos como
-
sed '1,6d' filename
-
sed -n '7,$p' filename
-
tail -n +7 filename
(e provavelmente outras variantes)
gravará todas as primeiras seis linhas de filename
para a saída padrão.
(Todos, é claro, leem todo o arquivo.)
Enquanto estamos nisso,
-
sed -n '1,6p' filename
-
sed '7,$d' filename
-
head -n 6 filename
-
sed '6q' filename
grava as 6 primeiras linhas de filename
na saída padrão.
Os dois primeiros podem ou não ler o arquivo inteiro;
os dois últimos provavelmente não o farão.
Além disso,
command input_filename > the_same_filename
não funciona, conforme discutido em
Aviso sobre ">" .
O que você talvez não saiba:
command arguments 1<> filename
abrirá filename
para leitura e escrita
sem truncar (estragar).
Então,
sed '1,6d' filename 1<> the_same_filename
pode ser o primeiro passo na solução que você está procurando.
Isso provavelmente é o mais próximo que você chegará
remover as primeiras linhas
M de um arquivo “in-loco”;
ele irá ler o arquivo e sobrescrevê-lo simultaneamente,
sem criar outro arquivo.
Se
M é pequeno o suficiente (ou, especificamente,
se o número de bytes nas primeiras linhas
M for pequeno o suficiente),
isto pode ler cada bloco do arquivo uma vez e escrever cada bloco uma vez -
e você não pode fazer nada melhor que isso.
Apenas o primeiro passo?
Eu criei este arquivo de teste:
$ cat -n foo
1 a
2 bcd
3 efghi
4 jklmnop
5 qrstuvwxy
6 z0123456789
7 ABCDEFGHIJKLM
8 Once upon a midnight dreary, while I pondered, weak and weary,
9 Over many a quaint and curious volume of forgotten lore—
10 While I nodded, nearly napping, suddenly there came a tapping,
11 As of some one gently rapping—rapping at my chamber door.
12 "'Tis some visitor," I muttered, "tapping at my chamber door—
13 Only this and nothing more."
14 The quick brown
15 fox jump over the
16 lazy dog. Once upon
17 this midnight dreary,
Este arquivo é meticulosamente construído
de modo que os comprimentos das linhas (incluindo novas linhas)
são 2, 4, 6, 8, 10, 12, 14 , 63, 57, 63, 58, 62, 63, 16, 18, 20 ,
e 22 .
Observe que as primeiras seis linhas, portanto, contêm 2 + 4 + 6 + 8 + 10 + 12 = 42 bytes.
As duas últimas linhas contêm 20 + 22 bytes, o que é coincidentemente (!) Também 42.
(O tamanho total do arquivo é 504.)
Então,
$ ls -l foo
-rw-r--r-- 1 myusername mygroupname 504 May 18 04:25 foo
$ sed '1,6d' foo 1<> foo
$ ls -l foo
-rw-r--r-- 1 myusername mygroupname 504 May 18 04:32 foo
$ cat -n foo
1 ABCDEFGHIJKLM
2 Once upon a midnight dreary, while I pondered, weak and weary,
3 Over many a quaint and curious volume of forgotten lore—
4 While I nodded, nearly napping, suddenly there came a tapping,
5 As of some one gently rapping—rapping at my chamber door.
6 "'Tis some visitor," I muttered, "tapping at my chamber door—
7 Only this and nothing more."
8 The quick brown
9 fox jump over the
10 lazy dog. Once upon
11 this midnight dreary,
12 lazy dog. Once upon
13 this midnight dreary,
OK, bom, as primeiras seis linhas desapareceram.
O número da linha original7 (“ABCDEFGHIJKLM”) é agora a linha número1.
Mas o que é isso?
O arquivo passou de 17 linhas para 13.
Deve ser 11 (17 a 6).
E as duas últimas linhas ("cachorro preguiçoso ... meia-noite triste") estão lá duas vezes.
Esta é uma das armadilhas do operador 1<>
-
se você não truncar o arquivo de saída,
você não pode acabar com um arquivo menor do que aquele com o qual você começou.
Especificamente, aqui, a saída de sed '1,6d' foo
é de 462 bytes
(504−42, desde as primeiras seis linhas contêm 42 bytes),
e por isso substitui os primeiros 462 bytes do arquivo de saída -
que também é foo
.
E os primeiros 462 bytes de foo
são todos menos os últimos 42 (504 a 462) -
então as duas últimas linhas não são sobrescritas.
As duas cópias das duas últimas linhas (“cachorro preguiçoso… meia-noite triste”)
são uma saída de sed
,
seguido por um que sobrou do conteúdo original do arquivo.
Então, o que vem depois?
Tudo o que precisamos fazer agora é jogar fora os últimos 42 bytes do arquivo.
Quando isso acontece, isso pode ser feito
apenas movendo o ponteiro que aponta para o final do arquivo.
OK, não é realmente um ponteiro; é um tamanho de arquivo inteiro
potAto, potAHto.
Nos últimos 20 ou 30 anos,
O Unix permitiu que você truncar um arquivo para o tamanho desejado,
deixando os dados até aquele ponto intocado,
e descartando os dados além desse ponto.
Um antigo comando que fará isso é
dd if=/dev/null bs=462 seek=1 of=foo 2> /dev/null
que copia /dev/null
sobre foo
, iniciando no byte 462.
Sim, é um pouco de kluge.
Um novo comando que faz essa função é
truncate -s 462 foo
Isso pode não estar presente em todos os sistemas; não é especificado por POSIX.
Então, juntando tudo,
#!/bin/sh
filename="$1"
bytes_to_remove=$(sed '6q' "$filename" | wc -c)
total_size=$(stat -c '%s' "$filename")
sed '1,6d' "$filename" 1<> "$filename"
new_size=$((total_size - bytes_to_remove))
truncate -s "$new_size" "$filename"
Usamos wc -c
para contar os caracteres nas primeiras seis linhas
(produzido por sed '6q'
), subtraia isso do tamanho total do arquivo,
e truncar o arquivo para esse tamanho.
Você pode usar qualquer um dos comandos alternativos
para produzir as primeiras linhas M ou as últimas linhas N-M ,
e você pode substituir a última linha com
dd if=/dev/null bs="$new_size" seek=1 of="$filename" 2> /dev/null
Advertências:
Eu não testei isso em arquivos com
- terminações de linha CR-LF ou
- caracteres multibyte,
e isso pode ser problemático.