O que você poderia fazer para evitar escrever uma cópia do arquivo é escrever o arquivo como:
{
sed "$l1,$l2 d" < file
perl -le 'truncate STDOUT, tell STDOUT'
} 1<> file
Perigoso, já que você não tem cópia de segurança.
Ou evitando sed
, roubando parte da ideia do manatwork:
{
head -n "$(($l1 - 1))"
head -n "$(($l2 - $l1 + 1))" > /dev/null
cat
perl -le 'truncate STDOUT, tell STDOUT'
} < file 1<> file
Isso ainda pode ser melhorado porque você está sobrescrevendo as primeiras linhas l1 - 1 , enquanto não é necessário, mas evitá-las significaria um pouco mais de programação e, por exemplo, faça tudo em perl
, o que pode acabar sendo menos eficiente:
perl -ne 'BEGIN{($l1,$l2) = ($ENV{"l1"}, $ENV{"l2"})}
if ($. == $l1) {$s = tell(STDIN) - length; next}
if ($. == $l2) {seek STDOUT, $s, 0; $/ = 768; next}
if ($. > $l2) {print}
END {truncate STDOUT, tell STDOUT}' < file 1<> file
Alguns horários para remover linhas de 1000000 a 1000050 da saída de seq 1e7
:
-
sed -i "$l1,$l2 d" file
: 16.2s - 1ª solução: 1,25s
- 2ª solução: 0,057s
- 3a solução: 0,48s
Todos trabalham com o mesmo princípio: abrimos dois descritores de arquivo no arquivo, um no modo somente leitura (0) usando < file
short para 0< file
e um no modo leitura-gravação (1) usando 1<> file
( <> file
seria 0<> file
). Esses descritores de arquivo apontam para duas descrições de arquivos abertos que terão cada uma posição atual do cursor dentro do arquivo associado a elas.
Na segunda solução, por exemplo, o primeiro head -n "$(($l1 - 1))"
lerá $l1 - 1
linhas de dados de fd 0 e grava esses dados em fd 1. Então, no final desse comando, o cursor em ambos descrições de arquivos abertos associadas aos fds 0 e 1 estarão no início da linha $l1
th.
Então, em head -n "$(($l2 - $l1 + 1))" > /dev/null
, head
lerá $l2 - $l1 + 1
linhas da mesma descrição do arquivo aberto através de seu fd 0 que ainda está associado a ele, então o cursor em fd 0 irá vá para o começo da linha depois do $l2
um.
Mas o seu fd 1 foi redirecionado para /dev/null
, portanto, ao escrever para o fd 1, ele não moverá o cursor na descrição do arquivo aberto apontada por {...}
's fd 1 .
Portanto, ao iniciar cat
, o cursor na descrição do arquivo aberto apontada por fd 0 estará no início da próxima linha após $l2
, enquanto o cursor em fd 1 ainda estará no início da linha $l1
th. Ou dito de outra forma, esse segundo head
terá pulado essas linhas para remover na entrada, mas não na saída. Agora, cat
sobrescreverá a linha $l1
th com a próxima linha após $l2
e assim por diante.
cat
retornará quando chegar ao final do arquivo no fd 0. Mas o fd 1 apontará para algum lugar no arquivo que ainda não foi sobrescrito. Essa parte tem que ir embora, corresponde ao espaço ocupado pelas linhas deletadas agora deslocadas para o final do arquivo. O que precisamos é truncar o arquivo no local exato onde o fd aponta para agora.
Isso é feito com a chamada do sistema ftruncate
. Infelizmente, não há nenhum utilitário Unix padrão para fazer isso, então recorremos a perl
. tell STDOUT
nos dá a posição atual do cursor associada ao fd 1. E nós truncamos o arquivo nesse deslocamento usando a interface do perl para a chamada do sistema ftruncate
: truncate
.
Na terceira solução, substituímos a gravação por fd 1 do primeiro comando head
com uma chamada de sistema lseek
.