Je veux parcourir un tas de répertoires et renommer tous les fichiers qui se terminent par _test.rb pour finir par _spec.rb à la place. C'est quelque chose que je n'ai jamais vraiment compris comment faire avec bash, alors cette fois j'ai pensé que je ferais un effort pour le faire clouer. Cependant, j'ai jusqu'ici échoué, mon meilleur effort est:
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
NB: il y a un écho supplémentaire après exec pour que la commande soit imprimée au lieu d'être exécutée pendant que je la teste.
Lorsque je l'exécute, la sortie de chaque nom de fichier correspondant est:
mv original original
c'est-à-dire que la substitution par sed a été perdue. C'est quoi le truc?
Réponses:
Cela se produit car
sed
reçoit la chaîne{}
en entrée, comme cela peut être vérifié avec:find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
qui imprime
foofoo
pour chaque fichier du répertoire, de manière récursive. La raison de ce comportement est que le pipeline est exécuté une fois, par le shell, lorsqu'il étend la commande entière.Il n'y a aucun moyen de citer le
sed
pipeline de manière àfind
l'exécuter pour chaque fichier, carfind
n'exécute pas de commandes via le shell et n'a aucune notion de pipelines ou de backquotes. Le manuel GNU findutils explique comment effectuer une tâche similaire en plaçant le pipeline dans un script shell séparé:#!/bin/sh echo "$1" | sed 's/_test.rb$/_spec.rb/'
(Il peut y avoir une manière perverse d'utiliser
sh -c
et une tonne de citations pour faire tout cela en une seule commande, mais je ne vais pas essayer.)la source
Pour le résoudre de la manière la plus proche du problème d'origine, il faudrait probablement utiliser l'option xargs "args par ligne de commande":
find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mv
Il trouve les fichiers dans le répertoire de travail actuel de manière récursive, fait écho au nom de fichier d'origine (
p
) puis à un nom modifié (s/test/spec/
) et les alimente tousmv
par paires (xargs -n2
). Attention, dans ce cas, le chemin lui-même ne doit pas contenir de chaînetest
.la source
-z
option avec les recherches-print0
et les xargs-0
:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
test
dossiers dans un même chemin.sed
ne renommera que le premier et lamv
commande échouera en cas d'No such file or directory
erreur.vous voudrez peut-être envisager une autre manière comme
for file in $(find . -name "*_test.rb") do echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/` done
la source
echo $file | sed s/_test.rb$/_spec.rb/
; fait est un one-liner, non?for
les divisera en mots séparés. Vous pouvez le faire fonctionner en demandant à la boucle for de se diviser uniquement sur les nouvelles lignes. Voir cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html pour des exemples.-exec
option de find.Je trouve celui-ci plus court
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
la source
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
fonctionne ? Comme le feraitfind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;
Vous pouvez le faire sans sed, si vous le souhaitez:
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done
${var%%suffix}
bandessuffix
de la valeur devar
.ou, pour le faire en utilisant sed:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
la source
sed
celui) comme l'explique la réponse acceptée.for i in... ; do ... ; done
, qui exécute des commandes via le shell et ne comprend backtick.Vous mentionnez que vous utilisez
bash
comme shell, auquel cas vous n'en avez pas réellement besoinfind
etsed
pour obtenir le changement de nom par lots que vous recherchez ...En supposant que vous utilisez
bash
comme shell:$ echo $SHELL /bin/bash $ _
... et en supposant que vous ayez activé l'
globstar
option dite du shell:$ shopt -p globstar shopt -s globstar $ _
... et enfin en supposant que vous avez installé l'
rename
utilitaire (trouvé dans leutil-linux-ng
package)$ which rename /usr/bin/rename $ _
... alors vous pouvez obtenir le changement de nom par lots dans un bash one-liner comme suit:
(l'
globstar
option shell garantira que bash trouve tous les*_test.rb
fichiers correspondants , peu importe à quel point ils sont imbriqués dans la hiérarchie des répertoires ... utilisezhelp shopt
pour savoir comment définir l'option)la source
Le moyen le plus simple :
find . -name "*_test.rb" | xargs rename s/_test/_spec/
Le moyen le plus rapide (en supposant que vous ayez 4 processeurs):
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/
Si vous avez un grand nombre de fichiers à traiter, il est possible que la liste des noms de fichiers acheminés vers xargs fasse en sorte que la ligne de commande résultante dépasse la longueur maximale autorisée.
Vous pouvez vérifier la limite de votre système en utilisant
getconf ARG_MAX
Sur la plupart des systèmes Linux, vous pouvez utiliser
free -b
oucat /proc/meminfo
trouver la quantité de RAM avec laquelle vous devez travailler; Sinon, utiliseztop
ou l'application de surveillance de l'activité de votre système.Un moyen plus sûr (en supposant que vous ayez 1000000 octets de RAM pour travailler):
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
la source
Voici ce qui a fonctionné pour moi lorsque les noms de fichiers contenaient des espaces. L'exemple ci-dessous renomme récursivement tous les fichiers .dar en fichiers .zip:
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;
la source
Pour cela, vous n'avez pas besoin
sed
. Vous pouvez parfaitement vous retrouver seul avec unewhile
boucle alimentée par le résultat d'find
une substitution de processus .Donc, si vous avez une
find
expression qui sélectionne les fichiers nécessaires, utilisez la syntaxe:while IFS= read -r file; do echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK! done < <(find -name "*_test.rb")
Cela les
find
fichiers et les renommer tous en séparant la chaîne_test.rb
de la fin et en l'ajoutant_spec.rb
.Pour cette étape, nous utilisons l' expansion des paramètres du shell où
${var%string}
supprime le modèle correspondant le plus court "chaîne"$var
.$ file="HELLOa_test.rbBYE_test.rb" $ echo "${file%_test.rb}" # remove _test.rb from the end HELLOa_test.rbBYE $ echo "${file%_test.rb}_spec.rb" # remove _test.rb and append _spec.rb HELLOa_test.rbBYE_spec.rb
Voir un exemple:
$ tree . ├── ab_testArb ├── a_test.rb ├── a_test.rb_test.rb ├── b_test.rb ├── c_test.hello ├── c_test.rb └── mydir └── d_test.rb $ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb") mv ./b_test.rb ./b_spec.rb mv ./mydir/d_test.rb ./mydir/d_spec.rb mv ./a_test.rb ./a_spec.rb mv ./c_test.rb ./c_spec.rb
la source
while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
find .... -exec mv ...
. Faites également attention$file
car il échouera s'il contient des espaces. Mieux vaut utiliser des citations"$file"
.si vous avez Ruby (1.9+)
ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
la source
Dans la réponse de ramtam que j'aime, la partie de recherche fonctionne bien mais le reste ne fonctionne pas si le chemin contient des espaces. Je ne suis pas trop familier avec sed, mais j'ai pu modifier cette réponse pour:
find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv
J'avais vraiment besoin d'un changement comme celui-ci car dans mon cas d'utilisation, la commande finale ressemble plus à
find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
la source
Je n'ai pas le cœur de tout recommencer, mais j'ai écrit ceci en réponse à Commandline Find Sed Exec . Là, le demandeur voulait savoir comment déplacer une arborescence entière, en excluant éventuellement un répertoire ou deux, et renommer tous les fichiers et répertoires contenant la chaîne "OLD" pour qu'ils contiennent à la place "NEW" .
En plus de décrire le comment avec une verbosité minutieuse ci-dessous, cette méthode peut également être unique en ce qu'elle intègre le débogage intégré. Il ne fait fondamentalement rien du tout tel qu'il est écrit, sauf compiler et enregistrer dans une variable toutes les commandes qu'il pense devoir faire pour effectuer le travail demandé.
Il évite également explicitement les boucles autant que possible. Outre la
sed
recherche récursive de plus d'une correspondance du motif, il n'y a pas d'autre récursivité à ma connaissance.Et enfin, il est entièrement
null
délimité - il ne se déclenche sur aucun caractère dans aucun nom de fichier à l'exception dunull
. Je ne pense pas que tu devrais avoir ça.Au fait, c'est VRAIMENT rapide. Regardez:
% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}" > read -r SED <<SED > :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p > SED > find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" | > sort -zg | sed -nz ${SED} | read -r ${6} > echo <<EOF > Prepared commands saved in variable: ${6} > To view do: printf ${6} | tr "\000" "\n" > To run do: sh <<EORUN > $(printf ${6} | tr "\000" "\n") > EORUN > EOF > } % rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}" % time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \ > ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \ > ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \ > | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 ) <actual process time used:> 0.06s user 0.03s system 106% cpu 0.090 total <output from wc:> Lines Words Bytes 115 362 20691 - <output from tail:> mv .config/replacement_word-chrome-beta/Default/.../googlestars \ .config/replacement_word-chrome-beta/Default/.../replacement_wordstars
REMARQUE: ce qui précède
function
nécessitera probablement desGNU
versions desed
etfind
pour gérer correctement lefind printf
etsed -z -e
et:;recursive regex test;t
appels . Si ceux-ci ne sont pas disponibles pour vous, la fonctionnalité peut probablement être dupliquée avec quelques ajustements mineurs.Cela devrait faire tout ce que vous vouliez du début à la fin avec très peu de complications. Je l'ai fait
fork
avecsed
, mais je pratiquais aussised
des techniques de branchement récursives, c'est pourquoi je suis ici. C'est un peu comme obtenir une coupe de cheveux à prix réduit dans une école de coiffeur, je suppose. Voici le flux de travail:rm -rf ${UNNECESSARY}
./app
pourrait être indésirable. Supprimez-le ou déplacez-le ailleurs au préalable, ou, alternativement, vous pouvez créer une\( -path PATTERN -exec rm -rf \{\} \)
routine pourfind
le faire par programme, mais celui-ci est tout à vous._mvnfind "${@}"
${sh_io}
est particulièrement important en ce qu'il enregistre le retour de la fonction.${sed_sep}
vient dans une seconde près; c'est une chaîne arbitraire utilisée pour référencersed
la récursion de la fonction. Si${sed_sep}
est défini sur une valeur qui pourrait potentiellement être trouvée dans l'un de vos noms de chemin ou de fichier sur lesquels vous avez agi ... eh bien, ne le laissez pas être.mv -n $1 $2
-noclobber
option définie pourmv
; comme écrit, cette fonction ne mettra pas${SRC_DIR}
là où un${TGT_DIR}
existe déjà.read -R SED <<HEREDOC
find . -name ${OLD} -printf
find
processus. Avecfind
nous ne recherchons que tout ce qui doit être renommé car nous avons déjà effectué toutes les opérations de placement à placemv
avec la première commande de la fonction. Plutôt que de prendre une action directe avecfind
, comme unexec
appel, par exemple, nous l'utilisons à la place pour construire dynamiquement la ligne de commande avec-printf
.%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
find
localisé les fichiers dont nous avons besoin, il construit et imprime directement (la plupart ) de la commande dont nous aurons besoin pour traiter votre changement de nom. Le%dir-depth
clouage au début de chaque ligne aidera à nous assurer que nous n'essayons pas de renommer un fichier ou un répertoire dans l'arborescence avec un objet parent qui n'a pas encore été renommé.find
utilise toutes sortes de techniques d'optimisation pour parcourir l'arborescence de votre système de fichiers et il n'est pas certain qu'il renverra les données dont nous avons besoin dans un ordre sûr pour les opérations. C'est pourquoi nous ...sort -general-numerical -zero-delimited
find
la sortie de%directory-depth
afin que les chemins les plus proches en relation avec $ {SRC} soient travaillés en premier. Cela évite les erreurs possibles impliquant desmv
fichiers dans des emplacements inexistants et minimise le besoin de bouclage récursif. ( en fait, vous pourriez avoir du mal à trouver une boucle )sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
%Path
imprimée pour chaque chaîne au cas où elle contiendrait plus d'une valeur $ {OLD} qui pourrait avoir besoin d'être remplacée. Toutes les autres solutions que j'ai imaginées impliquaient un deuxièmesed
processus, et même si une boucle courte n'est peut-être pas souhaitable, elle bat certainement la génération et la fourche d'un processus entier.sed
fait ici est de rechercher $ {sed_sep}, puis, après l'avoir trouvé, le sauvegarde et tous les caractères qu'il rencontre jusqu'à ce qu'il trouve $ {OLD}, qu'il remplace ensuite par $ {NEW}. Il revient ensuite à $ {sed_sep} et cherche à nouveau $ {OLD}, au cas où cela se produirait plus d'une fois dans la chaîne. S'il n'est pas trouvé, il imprime la chaîne modifiée dansstdout
(qu'il attrape ensuite à nouveau) et termine la boucle.mv
chaîne de commande, qui doit bien sûr inclure $ {OLD}, l'inclut, et la seconde moitié est modifiée autant de fois que nécessaire pour effacer le $ {OLD} nom dumv
chemin de destination de.sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
-exec
appels se produisent ici sans une secondefork
. Dans le premier, comme nous l'avons vu, nous modifions lamv
commande fournie parfind
la-printf
commande de fonction de s comme nécessaire pour modifier correctement toutes les références de $ {OLD} à $ {NEW}, mais pour ce faire, nous devions utiliser des points de référence arbitraires qui ne devraient pas être inclus dans le résultat final. Donc, une fois quesed
tout ce qu'il a à faire est terminé, nous lui demandons d'effacer ses points de référence du tampon de maintien avant de le transmettre.ET MAINTENANT NOUS SOMMES DE RETOUR
read
recevra une commande qui ressemble à ceci:% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Il sera
read
en${msg}
tant que${sh_io}
qui peut être examinée à volonté en dehors de la fonction.Cool.
-Mike
la source
J'ai pu gérer les noms de fichiers avec des espaces en suivant les exemples suggérés par onitake.
Cela ne casse pas si le chemin contient des espaces ou la chaîne
test
:find . -name "*_test.rb" -print0 | while read -d $'\0' file do echo mv "$file" "$(echo $file | sed s/test/spec/)" done
la source
C'est un exemple qui devrait fonctionner dans tous les cas. Fonctionne recursiveley, nécessite juste un shell et prend en charge les noms de fichiers avec des espaces.
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
la source
$ find spec -name "*_test.rb" spec/dir2/a_test.rb spec/dir1/a_test.rb $ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);' `spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb' `spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb' $ find spec -name "*_spec.rb" spec/dir2/b_spec.rb spec/dir2/a_spec.rb spec/dir1/a_spec.rb spec/dir1/c_spec.rb
la source
Votre question semble concerner sed, mais pour atteindre votre objectif de renommer récursif, je suggère ce qui suit, sans vergogne extrait d'une autre réponse que j'ai donnée ici: renommer récursif dans bash
#!/bin/bash IFS=$'\n' function RecurseDirs { for f in "$@" do newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g' echo "${f}" "${newf}" mv "${f}" "${newf}" f="${newf}" if [[ -d "${f}" ]]; then cd "${f}" RecurseDirs $(ls -1 ".") fi done cd .. } RecurseDirs .
la source
sed
fonctionnement sans échapper()
si vous ne définissez pas l'-r
option?Manière plus sûre de renommer avec les utilitaires find et le type d'expression régulière sed:
mkdir ~/practice cd ~/practice touch classic.txt.txt touch folk.txt.txt
Supprimez l'extension ".txt.txt" comme suit -
cd ~/practice find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;
Si vous utilisez le + à la place de; afin de travailler en mode batch, la commande ci-dessus renomme uniquement le premier fichier correspondant, mais pas la liste entière des correspondances de fichiers par «trouver».
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
la source
Voici un joli oneliner qui fait l'affaire. Sed ne peut pas gérer cela correctement, surtout si plusieurs variables sont passées par xargs avec -n 2. Une sous-station bash gérerait cela facilement comme:
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'
L'ajout de -type -f limitera les opérations de déplacement aux fichiers uniquement, -print 0 gérera les espaces vides dans les chemins.
la source
Je partage ce post car il est un peu lié à la question. Désolé de ne pas avoir fourni plus de détails. J'espère que cela aide quelqu'un d'autre. http://www.peteryu.ca/tutorials/shellscripting/batch_rename
la source
Voici ma solution de travail:
for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); done
la source