I have a directory structure like this:
Bookname Edition 1 Bookname Edition 1 type1.pdf Bookname Edition 1 type2.pdf Bookname Edition 2 Bookname Edition 2 type1.pdf Bookname Edition 2 type2.pdf I want to recursively change the name from Edition to Volume of the directory and the filenames in those directories.
I started off with this and that's fine if I'm in the directory:
for f in *.pdf; do a="$(echo $f | sed s/Edition/Volume/)" mv "$f" "$a" done Then I tried this to change all files under the directories, and that's when I got stuck...
Please can you tell me how to do this or give me a better way of doing this. There are 15000 PDFs in 100 directories.
34 Answers
If your structure only has two levels, you don't need to use recursion.
Don't parse filenames with sed. If you want to use regex and sed style syntax to rename files, use rename. If you're using Ubuntu 17.10, you need to install it
sudo apt install rename Then you can use pa4080's answer.
With rename, use the -n option for testing.
rename -n 'expression' file(s)You could also just use the mv command. As a one-line command:
for d in ./*/; do mv -v "$d" "${d/Edition/Volume}"; done; for f in ./*/*; do mv -v "$f" "${f/Edition/Volume}"; done You can use echo for testing with mv, ie echo mv -v source dest, but it gives inaccurate results here, unless you test and then run the loops separately.
As a script:
#!/bin/bash # rename directories first for d in ./*/; do mv -v "$d" "${d/Edition/Volume}" done # now rename files for f in ./*/*; do mv -v "$f" "${f/Edition/Volume}" done mv and rename recognise -- to indicate the end of options. Since all paths begin with ./ in this example, we do not need to use that, but if paths may begin with -, then use -- at the end of options, eg rename -n -- 's/foo/bar/' *, to prevent those paths being interpreted as options.
You could use the command rename two times to accomplish this task:
rename 's/Edition/Volume/' */ # rename the directories rename 's/Edition/Volume/' */*.pdf # rename the PDF files inside Here are two similar questions:
On multi- level directories, you can do it in one step, but the issue is that you need to make sure to rename the directories from bottom to top, to prevent the command or script to change the directory name before its content is changed.
In python, you can use:
os.walk in combination with
topdown=False In a script:
#!/usr/bin/env python3 import os import shutil import sys for root, dirs, files in os.walk(sys.argv[1], topdown=False): for f in files: shutil.move( root+"/"+f, root+"/"+f.replace("Edition", "Volume").strip() ) for dr in dirs: shutil.move( root+"/"+dr, root+"/"+dr.replace("Edition", "Volume").strip() ) Save the script as change_name.py, run it with the directory as argument:
python3 /path/to/change_name.py <directory> This works recursively on any number of levels.
4Use find with the -depth option combined with prename:
find [DIRS...] -depth | prename -n 's|Edition(?=[^/]*$)|Volume|' Explanation:
-depthselects directory children before their parents. A tool “consuming” the selected path names for renaming can then rename children before their parents.s|Edition(?=[^/]*$)|Volume|replaces the first occurrence ofEditionin the path name withVolumebut only if the remainder doesn't contain a/, i. e. it only applies to the last path name component (achieved by the positive look-ahead(?=[^/]*$)).-ntellsprenameto not actually rename the paths but to print how it would rename them. Remove this option to actually rename them.