6

What I am doing is converting mp3's with LAME. I already have an old script I wrote that works but I want to add to it this ability--to no longer delete the file but instead save it in a new root folder with sub-directories that match the using the path that it is already in.

This is the code I got off the Internet that I am using in my script to get the path:

 c=$FILENAME
 xpath=${c%/*} 
 xbase=${c##*/}
 xfext=${xbase##*.}
 xpref=${xbase%.*}
 path=${xpath}
 pref=${xpref}
 ext=${xfext}

The xpath and path give me the directory structure /home/user/working-root-directory/band-folder/album-name/

Using that technique, how do I script this to get just the band-folder into a separate variable and the album-folder into a separate variable?

Then I can use them to create new folders keeping all the mp3s in band album order to put them into a different root folder; this would eliminate me from having to move them myself so the next time I run my script I will not re-sample them again because they will no longer be in the working directoy path/folders and still have a back up copy of my files just in case.

1
  • 1
    I personally am shying away from shellscripts, and converting my scripts into Python. Usually they become much more readable, and thus maintainable in the future. Yes, it's a lot of work, and still ongoing, but even partway through I already enjoyed the benefits.
    – pepoluan
    Oct 25, 2014 at 6:24

5 Answers 5

8

When referring to $c if by filename you mean full-path to file then this job is really very easy. There are a few ways to do it.

Using just POSIX shell globs and parameter expansion you can do:

c='/home/user/working-root-directory/band-folder/album-name/music-file.mp3'
file=${c##*/}
album=${c#*"${c%/*/"$file"}"/}
band=${c#*"${c%/*/"$album"}"/}
band=${band%%/*} album=${album%%/*}

Basically it works by trimming a string from the tail of the variable and then trimming the results from the head of the variable. In the last line any unwanted remains from the tail are stripped away. It is perhaps easier to understand with real-world data. If you were to wrap the above snippet in a shell with set -x enabled, you'd see something along these lines printed to stderr:

+ c=/home/user/working-root-directory/band-folder/album-name/music-file.mp3
+ file=music-file.mp3
+ album=album-name/music-file.mp3
+ band=band-folder/album-name/music-file.mp3
+ band=band-folder
+ album=album-name

Another way you might do this is to use the shell's internal field separator to split the pathname on / boundaries.

set -f; IFS=/             #disable globbing so as not to gen more filenames
set -- $c                 #set the shell's arg array to split path
shift "$(($#-3))"         #remove all but relevant fields
band=$1 album=$2 file=$3  #assign values

Here is some more set -x output:

+ c=/home/user/working-root-directory/band-folder/album-name/music-file.mp3
+ set -f
+ IFS=/
+ set -- '' home user working-root-directory band-folder album-name music-file.mp3
+ shift 4
+ band=band-folder
+ album=album-name
+ file=music-file.mp3
6
  • The IFS approach is inventive: nice work.
    – jasonwryan
    Oct 25, 2014 at 7:14
  • @jasonwryan - $IFS, cd -P, $PWD, and / are made for each other.
    – mikeserv
    Oct 25, 2014 at 7:16
  • 1
    Yes. I was referring to the total solution (the shift and all): clever and illuminating: thanks.
    – jasonwryan
    Oct 25, 2014 at 7:21
  • 1
    @jasonwryan - me too. I referred to cd -P and the rest because while "$HOME"//////dir is a perfectly acceptable pathname, it defies $IFS. However, cd -P "$HOME"/////dir; set -- $PWD fixes that every time.
    – mikeserv
    Oct 25, 2014 at 7:28
  • 1
    @mikeserv thank you very much that did it, I have it up and running now - thanks again !
    – uxserx-bw
    Oct 28, 2014 at 0:50
2

I would hire zsh for this job:

$ c='/home/user/working-root-directory/band-folder/album-name/filename.mp3'

$ echo $c:h
/home/user/working-root-directory/band-folder/album-name
$ echo $c:h:h
/home/user/working-root-directory/band-folder
$ echo $c:h:h:h
/home/user/working-root-directory

(...)

$ echo $c:t    
filename.mp3
$ echo $c:h:t
album-name
$ echo $c:h:h:t
band-folder

(...)

If you have to use bash life is harder, you need to use temporary variables:

$ c='/home/user/working-root-directory/band-folder/album-name/filename.mp3'

$ x1=${c%/*}
$ echo $x1
/home/user/working-root-directory/band-folder/album-name
$ x2=${x1%/*}
$ echo $x2
/home/user/working-root-directory/band-folder

 (...)

$ y1=${c##*/}
$ echo $y1
filename.mp3
$ y2=${x1##*/}
$ echo $y2
album-name
$ y3=${x2##*/}
$ echo $y3
band-folder

(...)
2

The previous solutions seem overly complicated to me. The following seems to me to be the simpliest solution.

To get the directory name of a filename strings in bash

 dirname "$varcontainingfilename"

To get the ONLY the filename from a filename string in bash use

 basename "$varcontainingfilename"

From this base you should be able to easily do what you want.

Example executing this shell script as script /a/b/c would serve as an example

 #!/bin/sh
 set -u
 PATH='/bin:/usr/bin'
 export PATH
 filestring="$1"
 fn=`basename "$filestring"`
 dn=`dirname "$filestring"`
 echo Original File String "$filestring"
 echo Filename is "$fn"
 echo directory is "$dn"
 exit 0
1
  • 1
    And what if $filestring ends in some number of newline characters? Terminating filenames correctly is always problematic when the filename is gathered by the shell from some other process's stdout.
    – mikeserv
    Oct 25, 2014 at 7:01
2

To make "playing" with variables a little shorter

c=/home/user/working-root-directory/band-folder/album-name/music-file.mp3
for var in file album band 
do
  eval "$var=\${c##*/}"
  c=${c%\/${!var}}
done

echo $file $album $band
music-file.mp3 album-name band-folder

Other way is to use read command

IFS=/ read -r band album file  <<< ${c#"${c%/*/*/*}"/}
2
  • upvoted for the read thing - slick. I don't fully understand the !var thing though. What does it do? Though, after a moment's consideration, I do think the read thing breaks if there are any newlines in the expansion you feed it.
    – mikeserv
    Oct 25, 2014 at 10:05
  • @mikeserv ${!var} get the value of variable $var Regarding newlines I can say that it is very rare case to meet it in file/directory names.
    – Costas
    Oct 25, 2014 at 11:05
1

Zsh is a much more elegant solution, but if it is not your shell, you can useawk:

 printf "%s\n" "$path"
/home/user/working-root-directory/band-folder/album-name/music-file.mp3

band=$(awk -F/ '{print $(NF-2)}' <(printf "%s" "$path"))
printf "%s\n" "$band"
band-folder

album=$(awk -F/ '{print $(NF-1)}' <(printf "%s" "$path"))
printf "%s\n" "$album"
album-name

file=$(awk -F/ '{print $NF}' <(printf "%s" "$path"))
printf "%s\n" "$file"
music-file.mp3
1
  • @Costas Note that using $NF breaks if the path includes the file name...
    – jasonwryan
    Oct 25, 2014 at 7:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .