Eu tenho muitos arquivos de texto em um diretório e quero remover a última linha de todos os arquivos no diretório.
Como eu posso fazer isso?
Eu tenho muitos arquivos de texto em um diretório e quero remover a última linha de todos os arquivos no diretório.
Como eu posso fazer isso?
Respostas:
Você pode usar este bom oneliner se tiver o GNU sed
.
sed -i '$ d' ./*
Ele removerá a última linha de cada arquivo não oculto no diretório atual. Mudar -i
para GNU sed
significa operar no lugar e '$ d'
comandar a sed
exclusão da última linha (que $
significa última e que d
significa excluir).
-i
que é um GNUism, então isso é discutível, mas eu perderia minha barba antiga se deixasse de ressaltar que algumas versões mais antigas sed
do não permitem que você coloque espaço em branco entre o $
e o d
(ou, em geral, entre o padrão e o comando).
*(.)
glob para arquivos regulares, não sei sobre outros shells.
As outras respostas têm problemas se o diretório contiver algo diferente de um arquivo comum ou um arquivo com espaços / novas linhas no nome do arquivo. Aqui está algo que funciona independentemente:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f
: encontre os arquivos no diretório $dir
-type f
que são arquivos regulares;-exec
execute o comando em cada arquivo encontradosed -i
: edite os arquivos no local;'$d'
: delete ( d
) o último ($
) linha.'+'
: diz ao find para continuar adicionando argumentos sed
(um pouco mais eficiente do que executar o comando para cada arquivo separadamente, graças a @zwol).Se você não desejar descer para subdiretórios, poderá adicionar o argumento -maxdepth 1
a find
.
find
presente é mais eficiente escrito find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
não está presente no comando completo, por que colocá-lo na explicação?
-depth 0
não funciona (findutils 4.4.2), deveria ser -maxdepth 1
.
-exec
.
Usar o GNU sed -i '$d'
significa ler o arquivo completo e fazer uma cópia dele sem a última linha, enquanto seria muito mais eficiente truncar o arquivo no local (pelo menos para arquivos grandes).
Com o GNU truncate
, você pode fazer:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
Se os arquivos forem relativamente pequenos, isso provavelmente seria menos eficiente, pois ele executa vários comandos por arquivo.
Observe que, para arquivos que contenham bytes extras após o último caractere de nova linha (após a última linha) ou em outras palavras, se eles tiverem uma última linha não delimitada , dependendo da tail
implementação, tail -n 1
retornará apenas esses bytes extras (como GNU tail
), ou a última linha (delimitada corretamente) e esses bytes extras.
|wc -c
na tail
chamada? (ou a ${#length}
)
${#length}
não funcionaria, pois conta caracteres, não bytes, e $(...)
removeria o caractere de nova linha posterior ${#...}
, sendo desativado em um, mesmo que todos os caracteres fossem de byte único.
Uma abordagem mais portátil:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
Eu não acho que isso precisa qualquer explicação ... exceto, talvez, que neste caso d
é o mesmo que $d
uma vez ed
por seleciona padrão a última linha.
Isso não procurará recursivamente e não processará arquivos ocultos (também conhecidos como dotfiles).
Se você também deseja editá-los, consulte Como combinar * com arquivos ocultos em um diretório
[[]]
para []
, será totalmente compatível com POSIX. ( [[ ... ]]
é um Bashism).
[[
não seja um Bashismo )
Uma linha compatível com POSIX para todos os arquivos que começam recursivamente no diretório atual, incluindo arquivos de ponto:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Apenas para .txt
arquivos, de forma não recursiva:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Veja também: