J'ai de nombreux fichiers texte dans un répertoire et je souhaite supprimer la dernière ligne de chaque fichier du répertoire.
Comment puis-je le faire?
J'ai de nombreux fichiers texte dans un répertoire et je souhaite supprimer la dernière ligne de chaque fichier du répertoire.
Comment puis-je le faire?
Réponses:
Vous pouvez utiliser ce joli oneliner si vous avez GNU sed
.
sed -i '$ d' ./*
Il supprimera la dernière ligne de chaque fichier non masqué dans le répertoire actuel. Le commutateur -i
pour GNU sed
signifie fonctionner en place et '$ d'
commande sed
de supprimer la dernière ligne ( $
signifiant dernière et d
signifiant supprimer).
-i
ce qui est un GNUisme, donc c'est théorique, mais je perdrais ma vieille barbe si je ne soulignais pas que certaines anciennes versions de sed
ne vous permettent pas de mettre un espace entre le $
et le d
(ou, en général, entre le motif et la commande).
*(.)
pour glober des fichiers réguliers, je ne connais pas les autres shells.
Les autres réponses ont toutes des problèmes si le répertoire contient autre chose qu'un fichier normal ou un fichier avec des espaces / retours à la ligne dans le nom de fichier. Voici quelque chose qui fonctionne malgré tout:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f
: rechercher les fichiers dans le répertoire $dir
-type f
qui sont des fichiers réguliers;-exec
exécuter la commande sur chaque fichier trouvésed -i
: éditer les fichiers en place;'$d'
: supprime ( d
) la dernière $
ligne ( ).'+'
: indique à find de continuer à ajouter des arguments à sed
(un peu plus efficace que d'exécuter la commande pour chaque fichier séparément, grâce à @zwol).Si vous ne voulez pas descendre dans les sous-répertoires, vous pouvez ajouter l'argument -maxdepth 1
à find
.
find
ceci est écrit plus efficacement find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
n'est pas présent dans la commande complète, pourquoi le mettre dans l'explication?
-depth 0
ne fonctionne pas (findutils 4.4.2), il devrait plutôt l'être -maxdepth 1
.
-exec
.
Utiliser GNU sed -i '$d'
signifie lire le fichier complet et en faire une copie sans la dernière ligne, alors qu'il serait beaucoup plus efficace de simplement tronquer le fichier en place (au moins pour les gros fichiers).
Avec GNU truncate
, vous pouvez faire:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
Si les fichiers sont relativement petits, cela serait probablement moins efficace car il exécute plusieurs commandes par fichier.
Notez que pour les fichiers qui contiennent des octets supplémentaires après le dernier caractère de nouvelle ligne (après la dernière ligne) ou en d'autres termes, s'ils ont une dernière ligne non délimitée , en fonction de l' tail
implémentation, tail -n 1
ne renverra que ces octets supplémentaires (comme GNU tail
), ou la dernière ligne (correctement délimitée) et ces octets supplémentaires.
|wc -c
dans l' tail
appel? (ou a ${#length}
)
${#length}
ne fonctionnerait pas car il compte les caractères, pas les octets et $(...)
supprimerait le caractère de nouvelle ligne en queue donc ${#...}
serait désactivé d'un, même si tous les caractères étaient à un octet.
Une approche plus portable:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
Je ne pense pas que cela a besoin d' aucune explication ... sauf peut - être que dans ce cas d
est le même que $d
depuis ed
par défaut sélectionne la dernière ligne.
Cela ne cherchera pas récursivement et ne traitera pas les fichiers cachés (aka dotfiles).
Si vous souhaitez également les modifier, voir Comment faire correspondre * avec des fichiers cachés dans un répertoire
[[]]
à []
alors il sera entièrement compatible POSIX. ( [[ ... ]]
est un bashisme.)
[[
n'est pas un
One-liner conforme POSIX pour tous les fichiers commençant récursivement dans le répertoire courant, y compris les fichiers dot:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Pour les .txt
fichiers uniquement, de manière non récursive:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Regarde aussi: