Linux - NewbieThis Linux forum is for members that are new to Linux.
Just starting out and have a question?
If it is not in the man pages or the how-to's this is the place!
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.
Im trying to build and extend some scripts to reorganize my music collection.
I do have basic knowlodge of bash, but sometimes i run into errors, which i can't seem to solve on my own - which (i guess) would farly simple to solve.
My first question:
Code:
#!/bin/sh
DIR=${1:-/home/mainstream/Music/rename}
TYPE='.mp3'
addtags() {
echo id3v2 -D $1 #echo is only added for output
echo id3v2 -g Classical -y 2012 $1
}
for file in $(find $DIR -name \*$TYPE | head -10); do
addtags $file
done
I want to pass the full (path +) name as parameter to the addtag function, so i can i rename it accordingly.
The problem is that the list of arguments in the for statement is splitted by spaces (other than TABs and newlines, according to the default value of IFS) hence you will never see the complete file names inside the loop and the addtags function.
You can try to change field splitting behaviour, for example by setting IFS to newline:
Code:
#!/bin/sh
DIR=${1:-/home/mainstream/Music/rename}
TYPE='.mp3'
addtags() {
echo id3v2 -D $1 #echo is only added for output
echo id3v2 -g Classical -y 2012 $1
}
IFS='
'
for file in $(find $DIR -name \*$TYPE | head -10)
do
addtags $file
done
I assume you use the /bin/sh invocation , as in your example. On the other hand, if you used the /bin/bash, you could try process substitution and leave IFS untouched:
Code:
#!/bin/bash
DIR=${1:-/home/mainstream/Music/rename}
TYPE='.mp3'
addtags() {
echo id3v2 -D $1 #echo is only added for output
echo id3v2 -g Classical -y 2012 $1
}
while read file
do
addtags $file
done < <(find $DIR -name \*$TYPE | head -10)
Great that worked! Thanks allot for the help guys! I appreciate it!
OK, this is what i made for now:
Code:
#!/bin/bash
OLD_IFS="$IFS"
IFS='
'
DIR=${1:-/home/mainstream/Music/rename}
TYPE="*.mp3"
read -e -p "Enter Year: " -i "2012" YEAR
echo "Select a Genre"
select GENRE in "Classical" "Dubstep" "Hip-Hop" "Skip"; do
case $GENRE in
Classical ) echo $GENRE; break;;
Dubstep ) echo $GENRE; break;;
Hip-Hop ) echo $GENRE; break;;
Rap ) echo $GENRE; break;;
Skip ) exit;;
esac
done
addtags() {
echo id3v2 -D $1 #echo is only added for output
echo id3v2 -g $GENRE -y $YEAR $1
}
rename() {
#break input filename into path and name
#so that names with paths included are correctly handled
filepath=${file%/*}
newname=${file##*/}
mp3=${file##*/}
#remove junk characters from filename
newname=${newname//+([,\'])/}
regex='_\(final\)|\ \(CDQ\)|\ \(NoShout\)|\ \(CDQ \& Final\)|\ \(Dirty\)|\ \(Mastered\)|\ \(full\)'
newname=${newname//+($regex)/}
#replace delimiter
regex='\ \-\ |\_\-\_|\-\-'
newname=${newname//+($regex)/-}
regex='_featuring_\|_feat_\|_f._\|_ft_|_feat._'
newname=${newname//+($regex)/_ft._}
regex='\(feat._|\(feat_'
newname=${newname//+($regex)/\(ft._}
#replace all instances
newname=${newname//+(_and_)/_&_}
#change spaces (and tabs) to underscores in filename
#extglob reduces multiple spaces to single underscore
newname=${newname//+([[:space:]])/_}
#lowercase filename
newname=${newname,,}
#remove all initial "digit+[-_]" combinations
#again extglob handles variable numbers of digits
newname=${newname#+([0-9])[_-]}
#remove any final "-zzzz" strings
#similar to the last operation
#assumes the file ends in ".mp3", however
regex='zzzz|mst|alki|you|wus|htf|whoa'
newname=${newname/-+($regex).mp3/.mp3}
#remove any "-(nnn_bpm)" strings
#remember, spaces have been changed to underscores already
newname=${newname/-(+([0-9])_bpm)}
#you can continue adding as many changes as you want here.
#call mv to change the filename.
#I used -iv to make it interactive and verbose, but that's up to you.
if [[ $mp3 != $newname ]]
then
echo \.-------------------------------------------------------------\.
echo "Old: \"$mp3\""
echo "New: \"$newname\""
echo mv $DIR/$mp3 $DIR/$newname
echo \'-------------------------------------------------------------\'
fi
}
for file in $(find $DIR -name $TYPE | head -10); do
new_file=$(rename "$file")
echo $new_file
addtags $new_file
done
IFS="$OLD_IFS"
exit 0
That was my recommendation which is to do things like unSpawn has suggested:
- Always look for ways to debug steps, turning on -vx gives you output and progress of your script.
- Set variables for the outcome of commands and check them, or specifically test the outcome of commands via $?
- Always verify on the target system the paths to commands. Since I mostly do embedded, the structure of the paths is deterministic and further, there are no guarantees that all commands are available. So from the shell on the target system I make sure the command exists, is accessible via $PATH, and that I know the exact location where the command is; such as /bin, or /sbin and include the full path to the command within the script.
And thanks to unSpawn, I hadn't known the various ways to control vx in scripts, I always add it as two lines:
Code:
set -x
set -v
And then comment those out or not depending on whether or not I need to debug.
Last edited by rtmistler; 12-19-2012 at 09:49 AM.
Reason: Bad enclosure of inline code
Thanks for your help, that worked quite good.
I was already using set -vx (use it always), but as i already stated earlier: my bash programming knowledge is quite basic :-(
Seriously, it's such a common mistake, I think every time I come here I have to post this link 2 or 3 times.
Word-splitting and globbing expansion are always an issue any time you try to include parameter and command expansions (including importing data from files) inside other commands.
You only have two choices in such cases: you can keep everything together as a single argument by quoting them, or let the shell split it up everywhere it can. There's no completely safe way to split such data only where you want it to, except through use of a while+read loop, with null separators*.
How can I rename all my *.foo files to *.bar, or convert spaces to underscores, or convert upper-case file names to lower case? http://mywiki.wooledge.org/BashFAQ/030
( *Actually, a for loop on an array of individual lines/filenames would also work, but then the issue becomes first safely loading the array with the data. )
Incidentally, I noticed your interesting take on parameter substitution in your rename function. Since many of the patterns use the same format, I'd like to recommend a modification to remove a lot of that duplication.
(I'm only going to post the relevant part of the function)
Code:
rename() {
....
local -a re_m re_r
re_m=(
'_(final)| (CDQ)| (NoShout)| (CDQ & Final)| (Dirty)| (Mastered)| (full)'
' - |_-_|--'
'_featuring_|_feat_|_f._|_ft_|_feat._'
'(feat._|(feat_'
'_and_'
'[[:space:]]'
)
re_r=(
''
'-'
'_ft._'
'(ft._'
'_&_'
'_'
)
for i in "${!re_m[@]}" ; do
newname=${newname//+("${re_m[i]}")/"${re_r[i]}"}
done
.....
}
Basically, store the find/replace patterns in matching arrays first and loop over them. You can expand on this as necessary, of course.
By the way, you don't generally need to backslash-escape special characters when you first store them in variables like this, as long as you properly quote the strings when setting them and when expanding them. Unless the strings you're working on actually have backslashes in them?
In other words, backslashes/quotes are only needed when protecting a character from direct shell parsing. Once a string is stored in a parameter, everything in it is treated literally, except for the word-splitting and globbing expansion that's done if unquoted. (To be specific, quotes and backslashes are processed before the parameter expansion step, but word-splitting and globbing happen after expansion).
Finally, you should endeavor to explicitly scope function variables locally whenever possible.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.