Bash File Name Matching - Binary file .ogg matches !!!
Linux - GeneralThis Linux forum is for general Linux questions and discussion.
If it is Linux Related and doesn't seem to fit in any other forum then 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.
Bash File Name Matching - Binary file .ogg matches !!!
Hello,
I am trying to loop through a directory of songs to rename them from a text file. This block is inside another loop which controls which entry from the file is currently being searched for.
for i in $(ls *.ogg); do
#if i contains the number 'N_' which matches '$i' then we make the swap.
if grep "$NEWNAME" "$i"; then
echo "MATCH Found"
#Make rename here.
fi
#End of the Loop.
done
The problem is that $i is being treated as a binary file (obvious enough) so the if statment always returns true.
Thanks.
p.s. No I do NOT want to do this in Perl!!!
How can I just check it name against the name of the variable $i and not the actual file itself...
Giving a sample entry from the file might help as well as a sample filename.
Your grep command looks backwords.
Code:
for i in $(ls *.ogg); do
You could use globbing here instead:
for song in *.ogg; do
Code:
if grep "$NEWNAME" "$i"; then
Perhaps something like:
newname=$(grep "$song" $NEWNAMELIST) | cut d=' ' f=2)
I am guessing what the format of the file might be, but I don't know what the format of the original vs final names might be.
If the original names are all like "1001.ogg" and the entries are like "1001_New_Name.ogg", then you could use something like "${song#[[:digit:]]*_}" to get just the "New_Name.ogg" part and "${song%_*.ogg}" to return 1001.ogg.
So the rename command could be:
mv "${song#[[:digit:]]*_}" "${song%_*.ogg}.ogg"
Please post code in [code] tags to improve readability.
You don't need to execute ls to get a list of files - just use the pattern. This saves the invocation of an external program - this is something you should always strive to do. i.e.
Code:
# Do this:
for i in *.ogg; do
...
done
# Rather than this:
for i in $(ls *.ogg); do
...
done
When you use grep, it looks inside the named file(s). I think you're trying to check if the file name matches some pattern rather than that it contains some pattern. grep knows how to handle text files. It's purpose is to output the lines inside a file which match some pattern. ogg files are not text files, hence the warning.
If you want to test if the file name matches some pattern (e.g. starts with some digits followed by an underscore), consider using the case statement:
Code:
#!/bin/bash
shopt -s extglob
for i in *.ogg; do
case "$i" in
+([0-9])_*)
echo "MATCH: '$i'"
;;
*)
echo "no match '$i'"
;;
esac
done
Note the enabling of the shell option extglob. This enables the extended glo patterns, which includes +(pattern), which matches pattern one or more times. We exploit this to match characters in the range [0-9] (digits). Note this option is probablt bash specific - if you're using another shell, like ksh, it will not work.
Ok, your right, I am basically trying to right a small script that will rename a directory of nameless ogg files with names provided from a text file. This is for when I have encoded stuff and do not have an internet connection and could not be bothered to type in all the names ...
matthewg42, I am trying to go with your suggestion first of all. However I need to match $i against a value in another array (${anarray[$a]) as opposed to +([0-9])_*) .
I tried this:
Code:
for i in *.ogg; do
case "$i" in
+(${anarray[$a]})
echo "MATCH: '$i'"
;;
*)
echo "no match '$i'"
;;
esac
done
But it is giving me a syntax error ... Can you advise me further?
Using a string as an index to an array is an associative array (a map in C++ STL language, or a hash in Perl terminology). Sadly, bash doesn't really support associative arrays at the moment. I'm sure someone out there has a hackish way to do it, but for a nice clean and readable program, I'd recommend using a different shell, or even moving up a level of sophistication to a language like Perl or Python or awk. Awk is probably the smallest dependency, and it's installed by default on most unix-like OSes).
So here's some alternatives for associative arrays:
1. Use zsh. This is broadly compatible with bash scripts, so if you have a lot of script already written, it's probably the easiest option. Bare in mind it's not likely to be installed by default on most systems. Note that you must declare an associative array using th typedef -A statement first:
Code:
#!/usr/bin/zsh
typeset -A ar
ar["bananas"]="yellow"
ar["apples"]="green"
echo "apples are ${ar["apples"]}"
echo "bananas are ${ar["bananas"]}"
2. Use awk. This is probably the most portable because some implementation of awk is likely to be installed on pretty much any unix-like system.
Code:
#!/usr/bin/awk -f
BEGIN {
ar["bananas"]="yellow";
ar["apples"]="green";
print "apples are " ar["apples"];
print "bananas are " ar["bananas"];
}
Oh I see, regular arrays with numeric indexes should be fine.
You can use a case statement with a variable, but you must use the correct syntax - you missed a ")" after your variable.
Also, how are you setting the array? If you try to set an array item using a shell glob pattern, the pattern will be pre-expanded to match the pattern, so if you do this:
Code:
ar=(*.ogg)
...and you have some files in the working directory which match *.ogg, the array will contain a list of those files, not the pattern itself.
Thanks again for your help! I can now rename loads of untitled music files.
Max.
Code:
#!/bin/bash
#!/bin/bash
#Shell Script to Rename Music Files with names from a text file.
#This is intended to be used when you have
#encoded a cd but the computer did not have an internet
#connection so the files are all unnamed.
#Read in names from text file (provided from the command
#line) and assign them to an array.
#e.g. To encode Iron Maiden's Killers:
#./song_rename Iron_Maiden_Killers.txt
#The data file MUST take the format
#NUMBER_SONGNAME.ogg
#You could change .ogg to whatever you want
#but why would you want to ... ;-)
#Echo back the name of the text file to take the names from.
filename="$1"
echo CDROM Data File is:
echo -----
echo $filename
echo -----
filename="$1"
declare -a anarray #declare anarray to be... an array
#Ok, next I want to cycle through the text file and load each entry
#(one per line) into the array #named anarray.
exec 3<$filename
# opens $filename for reading giving it the identifyer "&3"
let counter=0
while read LINE <&3; do
anarray[$counter]=$LINE
((counter++))
#echo $LINE
done
exec 3>&-
#close file
#Display the array contents.
echo "-------------------------"
echo "Output The Array Contents"
echo "-------------------------"
for (( i = 0; i < $counter; i++)); do
echo ${anarray[$i]}
done
#Ok, We know from the prior loop how many entries (songs)
#we have. So we need another loop which will
#iterate the same number of times. For each iteration we want to
#get an entry from anarray. We then want #to loop through the
#directory with the songs and match that anarray entry against
#the song number in the #directory.
#When we make a match we replace the directory
#UNTITLED name with the name from the file.
#So 1_Untitled.ogg becomes 1_The_Ides_Of_March.ogg
shopt -s extglob
#The first loop will make a number of iterations
#equal to the number of entries in the text file.
for (( a = 0; a < $counter; a++)); do
#For each line, we need to grap the number
#of the song which must be the first bit of data
#e.g. 1_The_Ides_Of_March.ogg
#So we just get '1_' and assign this value to NEWNAME
#On the next cycle NEWNAME will get 2_
#And So On.
#We use this number to match the actual untitled file names
#in the directory against those in the text file.
#I do hope all this makes sense ...
echo "Searching for" ${anarray[$a]}
NEWNAME=$(echo ${anarray[$a]} | grep -o '[0-9]\+_')
echo NEWNAME IS $NEWNAME
#Ok, loop through every .ogg in the directory.
for i in *.ogg; do
case "$i" in
$NEWNAME*)
echo "MATCH: '$i'"
#Be safe and remove any spaces. Annoying things ...
SONG_NEWNAME=$(echo ${anarray[$a]} | sed -e s/" "/_/g)
SONG_NEWNAME=$SONG_NEWNAME".ogg"
echo $SONG_NEWNAME
#Ok, now we need to rename the file.
mv $i $SONG_NEWNAME
;;
*)
echo "no match '$i'"
;;
esac
#End of the Loop.
done
done
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.