Comment supprimer la dernière ligne de tous les fichiers d'un répertoire?

17

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?

ThehalfarisedPheonix
la source
6
Qu'as-tu essayé? unix.stackexchange.com/help/how-to-ask : "Partager vos recherches aide tout le monde. Dites-nous ce que vous avez trouvé et pourquoi cela ne répondait pas à vos besoins. Cela démontre que vous avez pris le temps d'essayer de vous aider vous-même , cela nous évite de réitérer des réponses évidentes, et surtout, cela vous aide à obtenir une réponse plus précise et plus pertinente! "
Patrick
Pourquoi l'idée que quelqu'un l'appliquant accidentellement à / etc ait une qualité très spéciale d'induction
grinçante

Réponses:

4

Si vous y avez accès vim, vous pouvez utiliser:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done
R Sahu
la source
16

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 -ipour GNU sedsignifie fonctionner en place et '$ d'commande sedde supprimer la dernière ligne ( $signifiant dernière et dsignifiant supprimer).

StefanR
la source
3
Cela générera des erreurs (et ne fera rien) si le dossier contient autre chose qu'un fichier normal, par exemple un autre dossier ...
Najib Idrissi
1
@StefanR Vous utilisez -ice 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 sedne vous permettent pas de mettre un espace entre le $et le d(ou, en général, entre le motif et la commande).
zwol
1
@zwol Comme je l'ai écrit, cela entraînera une erreur , pas un avertissement, et sed abandonnera une fois qu'il aura atteint ce fichier (au moins avec la version de sed que j'ai). Les fichiers suivants ne seront pas traités. Jeter les messages d'erreur serait une idée terrible car vous ne sauriez même pas que c'est arrivé! Avec zsh vous pouvez utiliser *(.)pour glober des fichiers réguliers, je ne connais pas les autres shells.
Najib Idrissi
@NajibIdrissi Hmm, vous avez raison. Cela me surprend; Je m'attendais à ce qu'il se plaigne du répertoire, mais passait ensuite au fichier suivant sur la ligne de commande. En fait, je pense que je vais signaler cela comme un bug.
zwol
@don_crissti J'ai aussi GNU sed v4.3 ... Je ne sais pas quoi vous dire, je viens de tester à nouveau. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi
11

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.

Najib Idrissi
la source
1
Hmm, mais contrairement aux autres réponses, cela descend dans les sous-répertoires. (En outre, avec les versions actuelles de findceci est écrit plus efficacement find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol
@zwol Merci, j'ai ajouté cela dans la réponse.
Najib Idrissi
-print0n'est pas présent dans la commande complète, pourquoi le mettre dans l'explication?
Ruslan
1
De plus, -depth 0ne fonctionne pas (findutils 4.4.2), il devrait plutôt l'être -maxdepth 1.
Ruslan
@Ruslan J'avais une première version où j'utilisais des xargs mais je m'en souvenais -exec.
Najib Idrissi
9

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' tailimplémentation, tail -n 1ne renverra que ces octets supplémentaires (comme GNU tail), ou la dernière ligne (correctement délimitée) et ces octets supplémentaires.

Stéphane Chazelas
la source
avez - vous besoin d' un |wc -cdans l' tailappel? (ou a ${#length})
Jeff Schaller
@JeffSchaller. Oups. wc -c était en effet destiné. ${#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.
Stéphane Chazelas
6

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 dest le même que $ddepuis edpar 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

don_crissti
la source
Agréable! Si vous passez [[]]à []alors il sera entièrement compatible POSIX. ( [[ ... ]]est un bashisme.)
Wildcard
@Wildcard - merci, changé (mais ce [[n'est pas un
bashisme
Un non-POSIX-ism, devrais-je dire. :)
Wildcard
3

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 .txtfichiers 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:

Caractère générique
la source