Remplacement des points dans le nom de fichier par des traits de soulignement à l'exception de l'extension

9

Quelqu'un peut-il suggérer comment renommer le nom de fichier:

head.body.date.txt

À:

head_body_date.txt

Existe-t-il une instruction sur une seule ligne pour effectuer le changement de nom sous Unix ?

SG
la source
9
Supposez-vous qu'il .tar.gzs'agit d'une extension? Bienvenue également à U&L!
EKons
2
Voulez-vous vraiment dire Unix par opposition à Linux? Quel Unix?
terdon
1
La réponse évidente est mv head.body.date.txt head_body_date.txt. Si vous avez d'autres restrictions ou de vrais exemples, veuillez mettre à jour votre question avec ces derniers. Dans l'état actuel des choses, il n'est pas clair si la question se réfère à un fichier avec ce nom explicite, un répertoire unique où tous les fichiers ont un format particulier, ou si vous souhaitez rechercher une hiérarchie de répertoires pour des noms particuliers et les modifier.
Kusalananda

Réponses:

9

Itérer sur les noms de fichiers et utiliser l'expansion des paramètres pour la conversion:

for f in *.*.*.txt; do i="${f%.txt}"; echo mv -i -- "$f" "${i//./_}.txt"; done

Le modèle d'extension des paramètres ${f//./_}remplace tous les .s par _s dans le nom de fichier ( $f).

Ce qui précède fera un essai à blanc, pour permettre le changement de nom réel, supprimez echo:

for f in *.*.*.txt; do i="${f%.txt}"; mv -i -- "$f" "${i//./_}.txt"; done

Si vous souhaitez gérer n'importe quelle extension, pas seulement .txt:

for f in *.*.*.*; do pre="${f%.*}"; suf="${f##*.}"; \
                     echo mv -i -- "$f" "${pre//./_}.${suf}"; done

Après avoir vérifié la suppression echode l'action réelle:

for f in *.*.*.*; do pre="${f%.*}"; suf="${f##*.}"; \
                     mv -i -- "$f" "${pre//./_}.${suf}"; done

Générique, pour un nombre arbitraire de points, au moins un:

for f in *.*; do pre="${f%.*}"; suf="${f##*.}"; \
                 mv -i -- "$f" "${pre//./_}.${suf}"; done
heemayl
la source
existe-t-il un moyen général s'il y a un nombre arbitraire de points?
Ciprian Tomoiagă
1
@CiprianTomoiaga Do:for f in *.*; do ...; done
heemayl
Je pourrais suggérer de mettre le *.*modèle directement dans l'exemple, car maintenant le code semble ne fonctionner qu'avec un nombre fixe de points.
ilkkachu
@ilkkachu Assez juste, édité.
heemayl
Merci beaucoup heemay, cela a fonctionné pour moi. et un peu d'apprentissage!
SG
4

avec perlbaserename

$ rename -n 's/\.[^.]+$(*SKIP)(*F)|\./_/g' head.body.date.txt 
rename(head.body.date.txt, head_body_date.txt)
  • \.[^.]+$(*SKIP)(*F) ignorer ce modèle et rechercher des correspondances alternatives
  • |\./_/gremplacer tout .par_

Ou, en utilisant l'anticipation négative

$ rename -n 's/\.(?![^.]+$)/_/g' head.body.date.txt 
rename(head.body.date.txt, head_body_date.txt)

Une fois que tout va bien, supprimez l' -noption

Sundeep
la source
3
rename 's/\.(?=[^.]*\.)/_/g' *.txt

Utilise le remplacement regex pour rechercher toutes les instances sauf le dernier .dans le nom de fichier (lookahead non capturant) et les remplacer par _. Généraliser *.*si vous le souhaitez.

(cette version particulière de renamesemble être installée via util-linux). J'utilise Ubuntu 12.04(oui, une machine sérieusement obsolète).

Jonathan Carroll
la source
2

bash:

old=head.body.date.txt oldb=${old%.*} olde=${old##*.} ; \
mv -- "$old" "${oldb//./_}.${olde}"
agc
la source
(1) Vous devez utiliser mv --, pour vous prémunir contre les «anciens» noms de fichiers commençant par -. (2) Vous devez mettre $oldentre guillemets ( "$old"), pour vous prémunir contre les "anciens" noms de fichiers contenant des espaces ou des caractères spéciaux pour le shell (comme *et ?). (3) Votre réponse échoue dans le cas quelque peu pathologique où l'extension contient un trait de soulignement: head.body.date.t_xtdevient renommé head_body_date_t.xt.
Scott
@Scott, merci. Le code est maintenant modifié selon les trois éléments mentionnés.
agc
1

En utilisant mv, sedet reven une seule ligne:

mv "head.body.date.txt" "$(echo head.body.date.txt | rev | sed 's/\./_/2g' | rev)"

Si vous souhaitez l'appliquer sur tous les txtfichiers du représentant actuel, l'utilisation de globes semble délicate en raison de la façon dont cela mvfonctionne, mais vous pouvez faire une forboucle dans une ligne:

for file in *.txt; do mv "$file" "$(echo $file | rev | sed 's/\./_/2g' | rev)"; done

Légèrement plus long, mais vous pouvez faire correspondre plusieurs motifs dans votre boucle!

Valentin B.
la source
2
(1) Arrghhh! Ne fais pas $(ls …); fais juste for file in *.txt. (2) Vous devez utiliser plus de citations: mv "$file" "$(…)".
Scott
Merci Scott, dûment noté et édité! Je suis encore un jeune en coquille, soyez gentil;)
Valentin B.
Avec le recul, utiliser renamecomme plusieurs autres l'ont suggéré dans leurs réponses semble plus simple ...
Valentin B.
-1
 mv head.body.date.txt head_body_date.txt

C'est aussi simple que cela pour un seul fichier à la fois. Vous ne spécifiez pas si vous souhaitez que cela se fasse de manière récursive dans un répertoire ou dans l'ensemble du répertoire, ce serait donc la solution la plus pratique et la plus rapide.

coderodour
la source