LinuxQuestions.org
Support LQ: Use code LQ3 and save $3 on Domain Registration
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Newbie
User Name
Password
Linux - Newbie This 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

Reply
 
Search this Thread
Old 12-16-2012, 01:55 PM   #1
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Rep: Reputation: 0
Need some help with bash scripting.


Hello!

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 output:
Code:
mainstream@dopamine-pc:~/Music$ ./foo.sh 
id3v2 -D /home/mainstream/Music/rename/audo
id3v2 -g Classical -y 2012 /home/mainstream/Music/rename/audo
id3v2 -D machine
id3v2 -g Classical -y 2012 machine
id3v2 -D -
id3v2 -g Classical -y 2012 -
id3v2 -D redemption.mp3
id3v2 -g Classical -y 2012 redemption.mp3
I want the output to be "id3v2 -g Classical -y 2012 /home/mainstream/Music/rename/audo machine - redemption.mp3" - so not the 'scattered output above.


I tried adding " or ' but that trick doesn't seem to work though.
Can anyone help?

Thanks

Last edited by mainstream; 12-16-2012 at 03:12 PM.
 
Old 12-16-2012, 02:05 PM   #2
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Original Poster
Rep: Reputation: 0
I know it has something to do with ` to escape the spaces, but i tried everything lol!
 
Old 12-16-2012, 03:07 PM   #3
cbtshare
Member
 
Registered: Jul 2009
Posts: 561

Rep: Reputation: 42
I'm not sure what you want to accomplish specifically here, so maybe you could show the output error and what you want the correct output to be
 
Old 12-16-2012, 03:10 PM   #4
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Original Poster
Rep: Reputation: 0
Quote:
Originally Posted by cbtshare View Post
I'm not sure what you want to accomplish specifically here, so maybe you could show the output error and what you want the correct output to be
Ok i edited the post the your liking. I want the output to be on one line, so i can pass the filename to id3v2 to change the tags.
 
Old 12-16-2012, 03:45 PM   #5
colucix
Moderator
 
Registered: Sep 2003
Location: Bologna
Distribution: CentOS 6.5 OpenSuSE 12.3
Posts: 10,453

Rep: Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941
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)

Last edited by colucix; 12-16-2012 at 03:52 PM.
 
1 members found this post helpful.
Old 12-16-2012, 04:31 PM   #6
cbtshare
Member
 
Registered: Jul 2009
Posts: 561

Rep: Reputation: 42
the above code works, just unset IFS after.
Code:
#!/bin/sh

IFS='
'

DIR=${1:-/home/stain/KILLERS}
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
unset IFS
 
Old 12-16-2012, 05:08 PM   #7
chrism01
Guru
 
Registered: Aug 2004
Location: Sydney
Distribution: Centos 6.5, Centos 5.10
Posts: 16,225

Rep: Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021Reputation: 2021
Unless there's more code, no need to unset IFS. It'll unset/revert when the script completes.
 
Old 12-16-2012, 05:25 PM   #8
colucix
Moderator
 
Registered: Sep 2003
Location: Bologna
Distribution: CentOS 6.5 OpenSuSE 12.3
Posts: 10,453

Rep: Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941Reputation: 1941
Well... actually the correct way is to save the default IFS and restore it later (if the script continues, as Chris notcied):
Code:
OLD_IFS="$IFS"
IFS='
'
#
# Use the new value of IFS here then reset the original:
#
IFS="$OLD_IFS"
 
1 members found this post helpful.
Old 12-17-2012, 09:51 AM   #9
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Original Poster
Rep: Reputation: 0
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
Output is:
Code:
mainstream@dopamine-pc:~/Music$ bash foo.sh 
Enter Year: 2012
Select a Genre
1) Classical
2) Dubstep
3) Hip-Hop
4) Skip
#? 1
Classical

id3v2 -D
id3v2 -g Classical -y 2012
It doesn't seem to pass to the rename() function?

Last edited by mainstream; 12-17-2012 at 10:08 AM.
 
Old 12-19-2012, 08:35 AM   #10
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Original Poster
Rep: Reputation: 0
Nobody knows what's happening?
 
Old 12-19-2012, 09:07 AM   #11
unSpawn
Moderator
 
Registered: May 2001
Posts: 26,944
Blog Entries: 54

Rep: Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731Reputation: 2731
Quote:
Originally Posted by mainstream View Post
Nobody knows what's happening?
Easiest way to debug this is to temporarily change the bang line from
Code:
#!/bin/bash
to
Code:
#!/bin/bash -vx
or set
Code:
set -vx
as the second line or execute the script as
Code:
/bin/bash -vx /path/to/script
BTW you really didn't need to mess with the IFS if you would have used a "while" instead of a "for" loop:
Code:
find $DIR -name $TYPE | head -10 | while read ITEM; do
     NEW_FILE=$(rename "${ITEM}")
     echo "${NEW_FILE}"
     addtags "${NEW_FILE}"
done
 
2 members found this post helpful.
Old 12-19-2012, 09:48 AM   #12
rtmistler
Member
 
Registered: Mar 2011
Location: Milford, MA. USA
Distribution: MontaVista, Ubuntu, MINT
Posts: 964
Blog Entries: 7

Rep: Reputation: 443Reputation: 443Reputation: 443Reputation: 443Reputation: 443
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
 
Old 12-20-2012, 10:18 AM   #13
mainstream
Member
 
Registered: Oct 2010
Location: localhost
Distribution: Ubuntu / Linux Mint
Posts: 54

Original Poster
Rep: Reputation: 0
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 :-(
 
Old 12-20-2012, 10:47 AM   #14
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Debian sid + kde 3.5 & 4.4
Posts: 6,823

Rep: Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946
UnSpawn beat me to it. As always, the real solution is:

Don't Read Lines With For!

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*.

A few other links along the same lines*

How can I read a file (data stream, variable) line-by-line (and/or field-by-field)?
http://mywiki.wooledge.org/BashFAQ/001


How can I find and deal with file names containing newlines, spaces or both?
http://mywiki.wooledge.org/BashFAQ/020


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. )
 
Old 12-20-2012, 11:32 AM   #15
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Debian sid + kde 3.5 & 4.4
Posts: 6,823

Rep: Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946Reputation: 1946
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.
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] bash scripting Ronayn Programming 11 03-20-2012 04:46 AM
bash scripting ninjafairy Programming 11 05-18-2011 10:00 AM
Reading a bash variable in bash scripting problem freeindy Programming 3 11-27-2008 02:29 AM
Bash scripting help arturhawkwing Linux - General 1 08-10-2006 11:54 AM
BASH Scripting ? eroica Programming 3 06-07-2004 07:51 PM


All times are GMT -5. The time now is 06:25 PM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration