ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
cd "$line" returns "No such file or directory" (e.g. ./brbooks: line 32: cd: Wetland Plants: No such file or directory, even though I know it is there and can see it..)
The file $ls_file has Books ($ls_file) as the first line for some reason
Basically I have some files named ch1.pdf, ch2.pdf, ch3.pdf, etc. in different folders and I want to rename them to Chapter 1.pdf, Chapter 2.pdf, Chapter 3.pdf, etc. If you need more information, please ask, and sorry if this is a dumb question.. this is my first attempt at bash scripting.
My script:
Code:
#!/bin/bash
#Print some help if needed
if [[ $1 == "-h" || $1 == "--help" ]]
then
echo "Usage:"
echo " ./batch_rename <directory> <chapters | appendices | covers>"
exit
fi
#Declare some variables..
ls_file=Books
#Make sure <directory> is a directory
if [[ ! -d "$1" || -L "$1" ]]
then
echo "Invalid directory"
exit
fi
#Then, navigate to it..
cd "$1"
#..and list contents to a file
ls > $ls_file
#Check to see what we need to rename..
if [[ $2 == chapters ]]
then
#..and rename it
while read line
do
cd "$line"
rename 's/^ch/Chapter /' *
cd ..
done <$ls_file
echo "Renamed ch# to Chapter #"
exit
elif [[ $2 == appendices ]]
then
#..or rename this..
while read line
do
cd "$line"
rename 's/^ax/Appendix /' *
cd ..
done <$ls_file
echo "Renamed ax[a-z] to Appendix [a-z]"
exit
elif [[ $2 == covers ]]
then
#..or rename this
while read line
do
cd "$line"
rename 's/^fmatt/Cover (Front)/' *
cd ..
done <$ls_file
echo "Renamed fmatt to Cover (Front)"
fi
Okay, after talking with a friend and some experimenting around a bit, I found the problem to my first problem.. apparently when calling cd "$line" I wasn't actually in directory $1. However, my new question is why this is? And how did the while loop pull in the correct $ls_file for arguments (which it did) if it isn't in the directory I specified? Is there a way to make sure it stays in directory $1 without putting cd "$1" right before cd "$line"?
I am still wondering what is happening with Problem #2 though.
Sorry, that is a typo on my part in my reply (and not being specific enough in original post), I meant to say I am not actually in $1, even though I have cd'ed there.
This appears to be the offending line. The directory you are in has only sub directories and zero files? (ie Wetland Plants is a directory?)
Code:
cd "$line"
rename 's/^ch/Chapter /' *
cd ..
Why? What I mean to say is that instead of changing in and out of directories, just call your command on the entire path:
Code:
rename 's/^ch/Chapter /' "$line/*"
Okay, I've worked through this and fixed the comment (me being lazy) and quotes around $1 (there will be no spaces in that directory).
$1 contains all directories $line but I get an error unless I say cd $1 inside the while..do..done loop, like so:
Code:
do
cd $1
cd "$line"
done <$ls_file
then I don't get an error, which is odd, because I inserted that echo ${PWD} after cd $1 and as the first statement in my while..do..done loop (removing the cd $1 in the loop) and it said my directory was $1 each time! So needless to say I am very confused why it isn't working correctly without saying cd $1 in the while loop.
As for doing it all on one line, that doesn't seem to work even with a cd $1 in the while loop (I tried "$line/*" and "${line}/*"), while my brutish way seems to work with the cd $1 in the loop.
And the $1 has one file ($ls_file) and two subdirectories, atm (it's just a testing directory.. don't want to change any actual files until I'm sure it works).
Hope everything I've said so far makes sense, and thanks for your replies.
I think all the changing of directories is just causing problems. Let us try a different attack.
The assumption of this script is that you will run it and provide the correct path, this being the name of a book.
Once this name has been provided, within each book directory are one / all of chapters, appendices or covers as file names.
So let us try something like:
Code:
#!/bin/bash
#Print some help if needed
if [[ $1 == "-h" || $1 == "--help" ]]
then
echo "Usage:"
echo " ./batch_rename <directory> <chapters | appendices | covers>"
exit
fi
#Make sure <directory> is a directory
if [[ ! -d $1 ]]
then
echo "Invalid directory"
exit
fi
case $2 in
chapter) old="ch"; new="Chapter";;
appendices) old="^ax"; new="Appendix";;
covers) old="^fmatt"; new="Cover {Front)";;
*) echo "You have not entered a valid choice!"
exit;;
esac
while read line
do
mv -v $line ${line/%$old*/$new}${line##*$old}
done< <(find $1 -type f -name "$old*")
Explanation is as follows:
1. I have chosen case over if only to make it a little cleaner, but you can change back.
2. As you can see the case now simply sets some variables to be used in a once off while loop as opposed to several
3. I have changed from rename to mv because during my testing I had a directory with the letters ch in it and it tried to change the name of the directory first which then caused an issue when trying to find the file. Also, because I am using paths and not changing into each directory I am unable to use the
start of line regex '^' as the line does not start with, for example, ch but rather just the file name I want to change which is not elsewhere in the string.
4. Lastly, I am using find instead of ls as it is more robust and also allows me to choose files only (from the -type f switch)
I am not saying this is the only way, but hoping it will help lead you to the solution you are looking for.
Wow grail, thanks! I appreciate the help, and it seems like it should work (find gives me the the correct thing). However, it doesn't work with spaces in the directory names (e.g. if "Wetland Plants" is a directory). Moreover, even removing the spaces it doesn't work for some reason? I get, for example:
Code:
mv: target `12.pdf' is not a directory
mv: target `21.pdf' is not a directory
mv: target `8.pdf' is not a directory
mv: target `16.pdf' is not a directory
mv: target `2.pdf' is not a directory
mv: target `6.pdf' is not a directory
..you get the point
in a directory like WetlandPlants containing ch1-ch25.
Also, I have a couple questions:
This line ${line/%$old*/$new}${line##*$old}. I read up on ${var##} sometime last night.. does the ##*$old remove that from $line? Also, what does the ${line/%$old*/$new} do? Why are those two things adjacent?
Why does done have an extra < next to it?
Thanks for the tip regarding case, that works really well (and I now know what esac means!)
No problems ... wasn't sure whether there would be spaces or not. The following changes should work:
Code:
#!/bin/bash
#Print some help if needed
if [[ $1 == "-h" || $1 == "--help" ]]
then
echo "Usage:"
echo " ./batch_rename <directory> <chapters | appendices | covers>"
exit
fi
#Make sure <directory> is a directory
if [[ ! -d books/$1 ]]
then
echo "Invalid directory"
exit
fi
case $2 in
chapter) old="ch"; new="Chapter";;
appendices) old="^ax"; new="Appendix";;
covers) old="^fmatt"; new="Cover {Front)";;
*) echo "You have not entered a valid choice!"
exit;;
esac
while read line
do
mv -v "$line" "${line/%$old*/$new}${line##*$old}"
done< <(find "books/$1" -type f -name "$old*"
${line/%$old*/$new} - replace everything at the end of the line starting with $old with value for $new - book/book_name/ch1.txt becomes book/book_name/Chapter
${line##*$old} - remove everything up until the old name, ie leave the number and extension - ch1.txt becomes 1.txt
Put both together to get new name - book/book_name/Chapter1.txt
Space and extra <() allows the find command to redirect output into while loop a line at a time.
Actually, I guess I have one further problem, when I went and actually did this, it failed for some of the book folders that had hyphens in them (but not all):
Algae-Anatomy, Biochemistry, and Biotechnology
Quantum-Mechanical Signal Processing and Spectral Analysis
Quantum Mechanics-An Introduction for Device Physicists and Electrical Engineers, Second Edition
The Fluid Mechanics of Astrophysics and Geophysics-Earth's Core and Lower Mantle
The Fluid Mechanics of Astrophysics and Geophysics-Fluid Dynamics and Dynamos in Astrophysics and Geophysics
(instead of placing it in the book folder, it placed it in the Folder containing the book folders, and renamed them to Algae-AnChapter 1.pdf, Quantum-MeChapter 1.pdf, Quantum MeChapter 1.pdf The Fluid MeChapter 1.pdf and The Fluid MeChapter 1.pdf, respectively, for all chapters)
Is this because -A or -M or such are.. control characters (I believe is the right word?), and make mv or find do something special? Is there a way to protect against this, for future reference? But if that is the case, why didn't it do it for the folder Circuits & Filters Handbook 3e-Analog and VLSI Circuits?
(A link to a reference guide would be fine as well)
It would be to do with the naming of those directories. Not sure about the first one:
Algae-AnChapter 1.pdf
But the other make sense as they have the letters ch at the point where there is a replacement:
Quantum-Mechanical Signal Processing and Spectral Analysis -> Quantum-MeChapter 1.pdf
Quantum Mechanics-An Introduction for Device Physicists and Electrical Engineers, Second Edition -> Quantum MeChapter 1.pdf
The Fluid Mechanics of Astrophysics and Geophysics-Earth's Core and Lower Mantle -> The Fluid MeChapter 1.pdf
The Fluid Mechanics of Astrophysics and Geophysics-Fluid Dynamics and Dynamos in Astrophysics and Geophysics -> The Fluid MeChapter 1.pdf
Circuits & Filters Handbook 3e-Analog and VLSI Circuits -> No ch
For Algae, that is my misremembering, is actually turned the files into Algae-Anatomy, BioChapter 1.pdf (sorry if I was misunderstood, I meant the files inside the directory, e.g. ch1.pdf ch2.pdf ch3.pdf (to ch8.pdf) that were in Algae-Anatomy, Biochemistry, and Biotechnology were renamed to Algae-Anatomy, BioChapter 1.pdf, Algae-Anatomy, BioChapter 2.pdf and Algae-Anatomy, BioChapter 3.pdf, (to Chapter 8.pdf) and were then placed in $1).
However, I did find a simple fix (using regular expressions), which is to change old to something like ch[0-9] since ch will only have a number after it.
Thanks again for your help grail!
Edit - here is the final script:
Code:
#!/bin/bash
#Print some help if needed
if [[ $1 == "-h" || $1 == "--help" ]]
then
echo "Usage:"
echo " ./brbooks <directory> <chapters|appendices|cover>"
exit
fi
#Make sure <directory> is a directory
if [[ ! -d $1 ]]
then
echo "Invalid directory"
exit
fi
case $2 in
chapters) match="ch[0-9]"; old="ch"; new="Chapter ";;
appendices) match="ax[a-z]"; old="ax"; new="Appendix ";;
cover) match="fmatt"; old="fmatt"; new="Cover (Front)";;
*) echo "You have not entered a valid choice!"
exit;;
esac
while read line
do
mv -v "$line" "${line/%$old*/$new}${line##*$old}"
done< <(find "$1" -type f -name "$match*")
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.