Tag Archives: script

A while-read Loop

I found myself in need of a way to optionally run the same line of code before a script exited.  Well… need… it’s what I wanted.  Anyway.

After some trouble sorting out some syntax issues and some confusion about how to get a while loop to depend upon some user input, I came up with this code snippet which I will do just what I wanted.

##
# by JamesIsIn

variable=y
while [ "${variable}" = "y" ]
do

   ## What you want repeated goes here.##
   ## What you want repeated goes here.##

     read -e -p "Do you want to go again? [y/n]"
     echo $REPLY
     ## variable="${REPLY,,}" ## after bash v4 ##
     ## variable="$( echo "${REPLY}" | tr '[A-Z]' '[a-z]' )" ## for Mac and bash < v4 ##
     echo $variable
     if [[ "${variable}" = y ]]; then
          echo "Ok!"
     elif [[ "${variable}" = yes ]]; then
          echo "Ok!"
     else
          echo "Damn!"
          variable=n
     fi
done

##

As you can see, the line which takes the REPLY and pushes it into variable is repeated.  This is because there is a simpler way to make the reply lower-case starting with bash v4.  I included both lines in case anyone wants to use this on older versions of bash (including on Macs which still use an archaic version of bash).  Just uncomment the line for your system.

Enjoy.

Share

Album Art Wallpaper Changer

If you’ve been following along you know all of my music is ripped or downloaded as FLAC and is living on an Ubuntu server here on my network. I do a fair job of scanning in the album art (there are stacks yet to be scanned because it takes too much time, but that is neither here nor there). I thought I’d like to make use of all these scanned covers as rotating background art on my hi-fi machine.

Really there are three problems. First, get a complete list of the cover images. Second, mount that location and list in a useful manner. And third, rotate (randomly) through those images as backgrounds.

First I tackled the mounting part. I already was mounting the music share (referred to as MusicShare in the script) so I simply added an administrative folder (referred to as zetc) in that share which included a folder to house the images. I could then place a shortcut in my Pictures folder on the client machine and make a call to that folder with whatever I used to rotate the images.

Next I wrote the necessary script, but I want to save that for the end so let’s talk about the application for changing the wallpapers now. I selected Wallch and it seems to have all the options I need and seems to work well enough. (I have it running on two machines currently and I have not seen any issues worth reporting.)

Wallch is in the standard repositories so you can locate it in the Ubuntu Software Center or install it using the Terminal (with sudo apt-get install wallch). The only unusual thing I did was add a custom time interval in the Preferences dialog as I wanted a 15 second interval (the included intervals went from 10 seconds to thirty). Set it to randomly select images and called that good.

Let’s look at this script briefly as that’s really the meat of this matter. There are issues with using symbolic links in smb shares (if I serve symbolic links I lose the use of non-Windows standard characters) so I created a folder (as mentioned above) to house the hard links to the found image files.

I didn’t care to sort out proper names for them as I won’t likely ever look in the folder or care about what any particular file is called, so the image links are all named incrementally without regard for album, artist, or location.

Finally, I didn’t want to concern myself with what may or may not be located in my links folder when I run the script again (to create a more current set of links), so I remove all files located in the links folder before filling it up again with the new image links.

Here is my script. I hope this helps you with your music art needs.

##
#!/bin/bash
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

directory="/media/MusicDrive/MusicShare/"
zedfile="/media/MusicDrive/MusicShare/zetc/CoverSlideshow/ZedList"
zedfolder="/media/MusicDrive/MusicShare/zetc/CoverSlideshow/SymLinks/"

find "$directory" -type f -name [Cc]over.[Jj][Pp]*[Gg] -o -name [Cc]over.[Pp][Nn][Gg] -o -name [Cc]over.[Bb][Mm][Pp] -o -name [Cc]over.[Tt][Ii][Ff] > "$zedfile"

declare -a zedfind
let i=0
while read zedline; do
zedfind[$i]="$zedline"
((i++))
done < "$zedfile"

echo "I have found" ${#zedfind[@]} "cover images."
echo
read -p "Press <ENTER> to coninue. "
echo
echo

rm "$zedfolder"/*

for (( ii=0 ; ii < ${#zedfind[@]} ; ii++ )) ; do
originalfile=$( basename "${zedfind[ii]}" )
echo "Creating link:" $ii-"$originalfile"
ln "${zedfind[ii]}" "$zedfolder"$ii-"$originalfile"
done

unset

exit

##
Share

Two-Finger Scrolling Unavailable—Not

So I’m using a different laptop.  It has a touchpad for mousing around, and on the touchpad are arrows indicating you are able to use edge scrolling.  This laptop is new enough it presumably has two-finger touch sensitivity, yet for whatever reason Unity’s System Preferences showed “Two finger scrolling” as greyed.

I prefer two-finger scrolling and thus I poked around the Webz to see what I could see.

I found this question asked and answered which had a solution to enable two-finger scrolling.  Essentially the solution (work-around really) is a collection of three commands which enable two-finger scrolling (why it is greyed is another matter for another time).

I tested it and it worked on my machine (a Lenovo w500) as well.  Great.

As advised in the article one could add those three lines as part of one’s start-up scripts (.bashrc in most cases), but I was feeling differently.  First I consolidated those three lines into one line using double-ampersands.  Then I created an alias called touchpad which contained those three-now-one lines of code.

Confusedly, this work-around only remained effective until the machine went to sleep.  I would understand any logout, but you’ll see from the code that this effects X and X must relaunch after sleep (or at least changes state enough to reset this).

Checking the man page for alias (which I had to find on-line as my bash in Ubuntu 14.04 doesn’t have it) I found that I could make an alias persist (or at least make it get recreated) by adding it to my .bash_aliases file in my home directory.

Now it works great.  I have added that alias to each user account I use on this laptop so it is easy to call.  Perhaps one day I’ll add the work-around elsewhere so I won’t have to instantiate it, but for now I have this.

Below you will see the code I used.  I hope this helps you today.

##
#!/bin/bash
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

# Here is my alias. The code between the single quotes is what you would need if your aim was merely to test-fix your touchpad.

alias touchpad='xinput --set-prop --type=int --format=32 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Pressure" 4 && xinput --set-prop --type=int --format=32 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Width" 8 && xinput --set-prop --type=int --format=8 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Scrolling" 1 0'

# Here is my .bash_aliases file.

cat .bash_aliases
# command to enable two-finger scrolling

alias touchpad='xinput --set-prop --type=int --format=32 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Pressure" 4 && xinput --set-prop --type=int --format=32 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Width" 8 && xinput --set-prop --type=int --format=8 "SynPS/2 Synaptics TouchPad" "Synaptics Two-Finger Scrolling" 1 0'

##
Share

A New Script for Skipping Tracks

A while back I wrote a simple script for skipping tracks (and a couple of other things) for Rhythmbox.  Now that Ubuntu is moving to Banshee as their default media library application I have tossed together a new script for use with Banshee.

The trouble with Rhythmbox was getting the system to understand, when attached remotely, which monitor contained Rhythmbox.  This was dealt with by making use of the $DISPLAY variable.  Banshee makes this problem a bit more difficult.  Banshee uses dbus and dbus is more difficult to guess at and more difficult to inquire.  A little research has given me a workable bit of code that gets me there.  (I am still researching this code either for better or safer or more elegant solutions.)

I have built in some logic to make the script handle situations where the user inputs a command (argument) that is not necessarily appropriate to the current state of Banshee. It should handle most of what you might (accidentally?) throw at it. I still want to give it some parsing abilities so that when it displays the currently playing information it’s more user friendly. I’ll just add that here when I get that built.

I have built a code section (in two parts) for seeking (forward and reverse). The problem is that in my version of Ubuntu there is a dependency bug (in dbus) so the seeking won’t work for Banshee from the command line. I have included it here for folks for whom this bug does not apply. Feel free to comment on your operating system and Banshee version if my code works for you.

To get the seek code working simply paste the two parts into the two places in the script where I have placed markers. (You may optionally uncomment (remove the #’s) from the two lines in the help section near the end of the script, but it’s not required for the seek code to function. If you are running Ubuntu 10.04 like me you won’t need the seek code unless you have dealt with the dbus bug yourself.)

##
#!/bin/bash
# Banshee Control Script
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

# First tell the system how to find the current running version of Banshee if you are running this script remotely.  If you only use the script locally you can comment out this export line.
# This command reports a host of permissions errors and doesn't need to display its output to the user so we divert STDERR to null.

export $(strings /proc/*/environ 2> /dev/null | grep DBUS_SESSION | tail -1)


# Now we can manipulate Banshee.
# Grab that variable...

argument=$1

## The first part of the seek code belongs here.



##



# ... and shake it!

   if [ "$argument" = "p" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [ "$pbstat" = "paused" ]; then
            printme="I am resuming playback."
            banshact="banshee --play"
            banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
         elif [ "$pbstat" = "idle" ]; then
            printme="I am beginning playback."
            banshact="banshee --play"
            banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
         elif [ "$pbstat" = "playing" ]; then
            printme="I am pausing playback."
            banshact="banshee --pause"
         else
            printme="Is Banshee even on?  I'm confused." 
         fi

   elif [ "$argument" = "play" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "idle" ]; then
         printme="I am beginning playback."
         banshact="banshee --play"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="I am resuming playback."
         banshact="banshee --play"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "playing" ]; then
         printme="Banshee is already playing."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "pause" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am pausing playback."
         banshact="banshee --pause"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is paused.  Use p or play to resume playback."
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "stop" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am stopping playback."
         banshact="banshee --stop"
         
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee was paused and I have now stopped it."
         banshact="banshee --stop"
         
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is already stopped."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

   elif [ "$argument" = "n" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="I am skipping to the next track."
         banshact="banshee --next"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is paused.  Use p or play to resume playback."
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi
   

   elif [ "$argument" = "r" ]; then
      printme="I am going to rewind now.  Doing this early enough will supposedly go to the previous track."
      banshact="banshee --previous"
      banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      
# I should be able to prettify this and account for null variables.
# what I want: You are listeing to song by so-and-so (x of y) from album [] [year] ti:me.

   elif [ "$argument" = "s" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      if [ "$pbstat" = "playing" ]; then
         printme="This is what is currently playing:"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "paused" ]; then
         printme="Banshee is currently paused here:"
         banshow="banshee --query-title --query-artist --query-track-number --query-track-count --query-album --query-disc --query-year --query-duration"
      elif [ "$pbstat" = "idle" ]; then
         printme="Banshee is currently stopped.  Use play to resume playback."
      else
         printme="Is Banshee even on?  I'm confused."
      fi

## The second part of the seek code belongs here.



##

   elif [ "$argument" = "c" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
      printme="Banshee is currently $pbstat."

   else
      printf "\nI'm sorry.  I only understand the following arguments:\n\n"
      printf "  p     -- attempt to toggle between play and pause\n"
      printf "  r     -- rewind to beginning or skip reverse\n"
      printf "  n     -- skip to next track\n"
      printf "  s     -- show what's playing\n"
      printf "  play  -- play\n"
      printf "  pause -- pause\n"
      printf "  stop  -- stop\n"
      printf "  c     -- display the current state of Banshee\n\n"
# You may want to uncomment the next two lines if you are using the seek code.
#      printf "  ff    -- fast forward (include seconds as ff 10)"
#      printf "  rr    -- rewind (include seconds as rr 5)"
      printf "Note that the arguments do not use a hyphen.  Please try again.\n\n"
      exit
   fi


$banshact
echo
echo $printme
echo
$banshow
echo


exit

##

As you can see, I have given play/pause toggling (where banshee itself doesn’t really have it) through the clever use of an if-loop (I’ll just pat myself on the back, ok?). Also it tells you what is playing anytime you start playing something (new or resumed).

Point of interest, there is bit of code that was not working in the script:

banshee –play; banshee –query-title –query-artist –query-track-number –query-track-count –query-album –query-disc –query-year –query-duration

Thing is if you paste this into a terminal it works perfectly. The problem is that semi-colon. On the command line it’s just fine, but in the script there must be a space after the --play or the shell will interpret the argument as literally --play; and it won’t work.

So the basic usage of the script is in this format:

Bs p

I placed this script in /usr/local/bin on the hi-fi machine. I called it merely Bs. That way the script is available to all users on that machine and is easy to call up from the terminal.

Be sure to make the file executable:

sudo chmod a+x /usr/local/bin/Bs

For those who would like to add the seek functionality, here it is. You can always test it on your system to see if it works and remove it (or comment it out) if it doesn’t.

##
#!/bin/bash
# Seek functionality for Banshee Control Script
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## This is the first part of the seek code.

seekseconds=$2
roundseek=$( printf "%0.f\n" $seekseconds )

##


## This is the second part of the seek code.

   elif [ "$argument" = "ff" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [[ $roundseek =~ ^[0-9]+$ ]]; then
            currentlocation=`banshee --query-position`
            currloc=${currentlocation#* }
            roundloc=$( printf "%0.f\n" $currloc )
            seekchange=$(( roundloc + roundseek ))
               if [ "$pbstat" = "playing" ]; then
                  printme="I am advancing playback by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "paused" ]; then
                  printme="Banshee is paused but I have advanced the pause location by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "idle" ]; then
                  printme="Banshee is neither playing nor paused and thus I am not able to advance anything"
                  banshact=""
                  banshow=""
               else
                  printme="Is Banshee even on?  I'm confused."
                  banshact=""
                  banshow=""
               fi
         else
            printme="When using ff you must enter seconds in the format \"Bs ff 5\" in order for me to process your request."
            banshact=""
            banshow=""
         fi   
      
   elif [ "$argument" = "rr" ]; then
      playbackstatus=`banshee --query-current-state`
      pbstat=${playbackstatus#* }
         if [[ $roundseek =~ ^[0-9]+$ ]]; then
            currentlocation=`banshee --query-position`
            currloc=${currentlocation#* }
            roundloc=$( printf "%0.f\n" $currloc )
            seekchange=$(( roundloc - roundseek ))
               if [ "$pbstat" = "playing" ]; then
                  printme="I am rewinding playback by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "paused" ]; then
                  printme="Banshee is paused but I have rewound the pause location by $roundseek seconds."
                  banshact="banshee --set-position=$seekchange"
                  banshow=""
               elif [ "$pbstat" = "idle" ]; then
                  printme="Banshee is neither playing nor paused and thus I am not able to rewind anything"
                  banshact=""
                  banshow=""
               else
                  printme="Is Banshee even on?  I'm confused."
                  banshact=""
                  banshow=""
               fi
         else
            printme="When using rr you must enter whole-numbered seconds in the format \"Bs rr 5\" in order for me to process your request."
            banshact=""
            banshow=""
         fi



##

Have fun with this one too. Suggestions are welcome.

Share

Malleable Renaming Script

I often get a pile of files where the original maker of those files used some naming scheme which, like spoiled milk, offends my nostrils.  You may call me anal if you’d like, but it prompted me to write this useful script so consider that before you start pointing fingers and laughing.

Essentially this script is set up so that I can easily open it in a text editor, make any changes, save it, and then run it on some folder.  Here it is and we’ll talk a bit below about how to use it.

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Substitute one string for another within a file name
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

#for filename in *
for filename in */*.[Ff][Ll][Aa][Cc]
#for filename in */*.[Mm][Pp]3
#for filename in */*.[Oo][Gg][Gg]
   do
   mv "$filename" "${filename/ - / – }"
   echo $filename changed
done

exit

 - – 

##

(Please note that your browser will likely not make a distinction between the hyphen (-) and the dash (–) in this post. However, if you click on the double-pointy-bracket button in the code box above you will be able to copy the correct code.)

Ok, so what we have here, after my usual header section, is four possible for loops. Three of them are commented out (since you’ll only be using one at a time), but they are pretty strait-forward.

The first one seeks anything in the current directory (know where you are!) and uses that as input. I use this first one for changing folder names, but be careful because it will change all folders and files that match the pattern which follows. The next three for loops are for three different kind of audio file (FLAC, OGG, and MP3). Since the capital and lower-case letters are in square brackets this means the shell is offered a choice. This is one method for eliminating case-sensitivity (there are others).

After the for loop begins and files/folders are sought out which match the patter under in we have to do something. In this case the script does two somethings: mv and echo (which mean “move” and “display in the terminal” basically).

The variable (here called filename) is then used to stimulate a substitution. Let’s dissect the substitution briefly. In the case shown above the shell will attempt to replace space-hyphen-space with space-dash-space in any flac file it finds in any folder below the current directory.

Why?

First */*.[Ff][Ll][Aa][Cc] tells us, in more human terms, look into any folder (*/) below the current directory for anything (*) that ends in .flac (.[Ff][Ll][Aa][Cc]). When you find one (do) replace the first (/) space-hyphen-space () with (/) space-dash-space () and that’s it (/).

If instead you use “${filename// – / – }” this means when you find one (do) replace all occurrences (//) of space-hyphen-space () with (/) space-dash-space () and that’s it (/).

There is no need to echo the work, but I like to see what it’s doing (or what it’s failed to do). It’s useful but not required. Also the exit command is not, strictly speaking, required; but I use it in this script especially so that I can store characters below it and thereby keep them around for use in the script ( – – ).

Let’s look at an example of this script in use.

Suppose you downloaded (legally, you sneaky bastards) a copy of Jay Farrar and Benjamin Gibbard’s One Fast Move or I’m Gone (Music from Kerouac’s Big Sur). Further suppose that the yahoo who made the flac files insisted on including the artist names on every track. Silly because you keep your collection organized and the album is in a folder with the album name which is in a folder with the artist name. So you’d like to remove the artist names from all the tracks. You could do that by hand for each track, but this is a perfect opportunity for a script so…

The tracks are currently Jay Farrar & Benjamin Gibbard 01 California Zephyr.flac and so your script would look like this:

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Substitute one string for another within a file name
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

#for filename in *
for filename in */*.[Ff][Ll][Aa][Cc]
#for filename in */*.[Mm][Pp]3
#for filename in */*.[Oo][Gg][Gg]
   do
   mv "$filename" "${filename/Jay Farrar & Benjamin Gibbard /}"
   echo $filename changed
done

exit

 - – 

##

Note two things. First there is a space after Gibbard. Fail to include that and all your tracks begin with a space. Second there is nothing between the slash and the curly-bracket. This means “replace Jay-space-Farrar-space-ampersand-space-Benjamin-space-Gibbard-space” with nothing (or delete it, eh?). This will leave your tracks looking more like 01 California Zephyr.flac .

If you are like me, you like having a dash between the track number and the track name. Others might prefer to use a dot. So for inserting a dash or a dot in the first occurrence of a space that line would look like this:

mv “$filename” “${filename/ / – }”

mv “$filename” “${filename/ /. }”

To run the script open a terminal and move (cd) into the artist folder keeping in mind it would change any other album folder contents—but I know these guys only put out one ablum in this configuration. If you are worried about changing other albums move into the album folder and remove the */ before the *.extension and the script will act on the current directory. When you are in the directory you want, run the script by name (I call mine RemoveModifiable.sh). Be sure the script has execute permissions.

Do be careful with this script. It can be really powerful and can leave you staring at a folder full of files called 01.flac pretty quickly (or several folders if you use the */ construction I am above). As always, work from copies. After all scripts are supposed to make your life easier.

Share

Recursive Prepending Script

As you may recall I prefer to prepend my track names with the album number in the case of multiple disc albums (box sets, double-albums, &c).  I have some rather large (30 CD) sets and am lazy didn’t want to run my other script 30 times, so I wrote this new version to deal with matters recursively.

(There is now an improved version of this script available here. The new version allows the user to select file extensions while the version on the page below is specifically written for FLAC files.)

(Use the pointy brackets button to copy and paste.)

##
#!/bin/bash
# Prepend album number before track numbers automatically and recursively
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## gather information

# obtain directory in which to work

echo
echo
echo "This script changes FLAC file names under a containing folder which you supply."
echo "It assumes that your discs are separated into numbered sub-folders."
echo "As such sub-folder names should be 01, 02, and so forth."
echo
echo "Example:  \"01/01 – Track Name.flac\" becomes \"01.01 – Track Name.flac\""
echo

read -e -p "I will require the path to the containing folder: "
   if [ -d "$REPLY" ]; then
      printf "\n\nI have confirmed this is a directory.\n"
      directory="$REPLY"
   else
      printf "\n\nI cannot find this directory.\n\n"
      echo "$REPLY"
      exit 1
   fi

read -p "Press <ENTER> to continue (ctrl-c will abort)."
   echo

## do the work

# find all two-digit numerically-named sub-directories under root directory

find "$directory" -type d -name [0-9][0-9] > /tmp/whyme

echo

# parse disc folders into array

declare -a discfind
   let i=0
   while read discline; do
      discfind[$i]=$discline

      # extract disc number from folder path
      disc=$( basename "$discline" )
      discpath=$( dirname "$discline" ) 

      # change file names in sub folder

      for filepath in "/$discpath/$disc/"*.[Ff][Ll][Aa][Cc]; do
         filename=$( basename "$filepath" )
         mv "$discpath/$disc/$filename" "$discpath/$disc.$filename"
         echo $filename changed
      done

      ((i++))
   done < /tmp/whyme

rm /tmp/whyme

exit

##

There are a couple of assumptions that this script makes (which are consistent with my preferences).

For instance, I like my file names to run album number dot track number (01.01 – Track Name.flac). As such I name the containing folders each a two-digit number based on the disc number (disc number three goes into a folder called 03). The script then uses that folder name or disc number (since they are now the same) as the prepend for the file name.

I hope that helps to make your life a little better.

Thanks for stopping.

Share

Script for Skipping Tracks

I use Rhythmbox to play music on my hi-fi system.  There is a command line element for Rhythmbox called rhythmbox-client.  This element can be used to initiate a series of commands in Rhythmbox.  The problem is that these commands end up being a bit longish if you are attached to that machine via ssh.  Here is an example:

DISPLAY=:0.0 rhythmbox-client –play-pause

That’s a lot of typing especially when you are attached via ssh from your Android phone.  It was just too tedious to type all that out on that on-screen keyboard.  So I wrote a script that would manage the arguments I felt were most important.

##
#!/bin/bash
# by JamesIsIn from JamesIsIn.com
# Do something nice today.


   if [ "$1" = "n" ]; then
      argument="--next --print-playing"
      printf "\nI am skipping to the next track:\n\n"
   elif [ "$1" = "p" ]; then
      argument="--play-pause"
      printf "\nI am toggling between play and pause.\n\n"
   elif [ "$1" = "s" ]; then
      argument="--print-playing"
      printf "\nThis is what is currently playing:\n\n"
#   if [ "$1" = "q" ]; then
#      $argument="--enqueue"
   else [ argument = "" ]; 
      printf "\nI'm sorry.  I only understand the following commands:\n\n"
      printf "p (play/pause)\n\n"
      printf "s (show what's playing)\n\n"
      printf "n (skip to next track)\n\n"
      printf "Please try again.\n\n"
      exit
      # I could add --enqueue but I have to figure out to make it work
   fi


# assuming DISPLAY does not matter for local runs (though this is for a single monitor configuration)

DISPLAY=:0.0 rhythmbox-client $argument
echo


exit

##

The use of DISPLAY is required so that rhythmbox-client knows on which monitor Rhythmbox is present.  The configuration above is for a single monitor arrangement.  You’re on your own to figure out what you need in there if you have a different monitor configuration.

So, using my script, if you wanted to issue the same command I mentioned above you would merely type:

Rb p

(Now I just need to streamline the ssh command and this whole procedure will be much easier and thereby more impressive.)

I placed this script in /usr/local/bin on the hi-fi machine. I called it merely Rb. That way the script is available to all users on that machine. Be sure to make the file executable:

sudo chmod a+x /usr/local/bin/Rb

(Since I use my server as a proper server now I moved my script to the share: /media/[share]/Rb so that it would be available across the network.)

Have fun with that.

Share

Conquer Giant APE and Giant FLAC

I have already written posts on splitting and converting from either album-length APE files or album-length FLAC files into track-length FLAC files.

(My post on converting APE files; my post on splitting APE files; my post on splitting FLAC files. You will want to go over these other articles if you have not done any of this before as there are some dependencies which you will not likely have installed.)

That was rather satisfying, but I then found that I would locate entire discographies where every album was a single APE or FLAC.  I did not enjoy the prospect of running each command on each pair of files.

That’s why scripting languages exist.  It’s a good thing I bought that BASH book.

Anyway, this script is designed to recursively locate APE, FLAC, and CUE files under a user-supplied root directory; then it runs shntool and cuetag on each APE/FLAC and CUE pair.  (You can read my previous posts linked above for more details about how these two commands work.)

It even cleans up after itself.  Since it deletes not only the temporary files it creates but also all the APE, FLAC, and CUE files it uses, you will want to pay close attention when the script tells you it is about to clean up all those files.  If you tell it to proceed (by hitting ENTER) without checking its work all those files will be deleted.  As such you should work from copies and you should check that it did what you wanted it to do before hitting ENTER when it asks about cleaning up all files.

Feel free to suggest any improvements.  I hope you find that it works well and improves your lives.

One important thing to note is that it is not an intelligent script.  It cannot determine for you whether a given APE or FLAC file is actually album length.  It assumes that you are offering it a folder (with or without sub-folders) which contains only album-length APE or album-length FLAC files with an accompanying CUE file (one matching pair per containing folder).

Sometimes a cue file will be formatted incorrectly or have some other issue.  Sometimes they can be fixed by opening them in a text editor.  Probably they can always be fixed in a text editor, but it might be difficult to determine what’s wrong with the CUE file.  If a CUE files has issues you will see errors on the command line (if you are staring at your monitor) and the album-length file will not be split/tagged.  You’ll have to work on that pair separately, but since you are working from copies it won’t matter if they get deleted.

If you throw something unusual at it, it’s hard to imagine how it might behave.  It’s likely not dangerous but you should always work from copies and preserve the originals until you are satisfied.

##
#!/bin/bash
# covert APE/FLAC albums to FLAC tracks recursively
# by JamesIsIn from JamesIsIn.com
# Do something nice today.

## gather information

# obtain directory in which to work

printf "\nHello.\n\n"
read -e -p "Please provide the root folder under which I will work recursively: "
if [ -d "$REPLY" ]; then
printf "\nI have confirmed this is a directory.\n\n"
directory="$REPLY"
else
printf "\nI cannot confirm this is a directory.\n\n"
echo "$REPLY"
exit 1
fi

# number, performer, and track names

printf "There are two formatting options for file naming (assuming the cue contains correct information):\n\n"
printf "01 – Track Name.flac\n"
printf "01 – Performer Name – Track Name.flac.\n\n"
read -p "Do you want to include the performer name in the file names (this is only recommended for multiple-artist albums)? [Y or N]: "
if [ "$REPLY" = "Y" ]; then
printf "\nOk, I'll include performer names: 01 – Performer Name – Track Name.flac\n\n"
trackname="%n – %p – %t"
elif [ "$REPLY" = "y" ]; then
printf "\nOk, I'll include performer names: 01 – Performer Name – Track Name.flac\n\n"
trackname="%n – %p – %t"
else [ "$REPLY" = "[Nn]" ];
printf "\nOk, I won't include performer names: 01 – Track Name.flac\n\n"
trackname="%n – %t"
fi

printf "\n\nLet's get to work.\n\n\n"

## do the work

# parse files into respective arrays recursively

find "$directory" -type f -name \*.[Aa][Pp][Ee] -o -name \*.[Ff][Ll][Aa][Cc] > /tmp/whyme

declare -a albumfind
let i=0
while read albumline; do
albumfind[$i]=$albumline
((i++))
done < /tmp/whyme

find "$directory" -type f -name \*.[Cc][Uu][Ee] > /tmp/whyme2

declare -a cuefind
let i=0
while read cueline; do
cuefind[$i]=$cueline
((i++))
done < /tmp/whyme2

# follow array counts

printf "\n\nCounts are as follows. There are "${#albumfind[@]}" albums and "${#cuefind[@]}" cues. Your albums ought to equal your cues. If these counts are not as expected, please inspect the contents of the folder you provided before proceding or re-run the script and select a different folder.\n\n"
read -p "Press <ENTER> to coninue. "

# split apes and flacs; hide album files

for (( i=0 ; i < ${#albumfind[@]} ; i++ )) ; do
# path is cue iteration less file name
albumfolder="${cuefind[i]%/*.*}"
shnsplit -d "$albumfolder" -o flac -f "${cuefind[i]}" -t "$trackname" "${albumfind[i]}" ;
mv "${albumfind[i]}" "${albumfind[i]}".hideme ;
done

# remove 00 files

find "$directory" -type f -name 00\*.[Aa][Pp][Ee] -o -name 00\*.[Ff][Ll][Aa][Cc] > /tmp/whyme3

declare -a deadfiles
let i=0
while read deadline; do
deadfiles[$i]=$deadline
((i++))
done < /tmp/whyme3

for (( i=0 ; i < ${#deadfiles[@]} ; i++ )) ; do
mv "${deadfiles[i]}" "${deadfiles[i]}".hideme ;
done

# tag files

for (( i=0 ; i < ${#cuefind[@]} ; i++ )) ; do
# path is cue iteration less file name
albumfolder="${cuefind[i]%/*.*}"
cuetag "${cuefind[i]}" "$albumfolder"\/*.flac
mv "${cuefind[i]}" "${cuefind[i]}".hideme ;
done

## clean-up

printf "\n\nI have completed all operations to the best of my ability.\nI will now clean up all files.\n\nPlease ensure you are satisfied with the state of things before you continue."
printf "\n\nPlease note: I am about to delete all the album-length files I believe I have used."
read -p "Press <ENTER> to coninue (Ctrl-c will abort clean-up). "

# remove temp files, hideme files, and unset all variables

find "$directory" -type f -name *.hideme > /tmp/whyme4

declare -a hidemes
let i=0
while read hidelines; do
hidemes[$i]=$hidelines
((i++))
done < /tmp/whyme4

for (( i=0 ; i < ${#hidemes[@]} ; i++ )) ; do
rm "${hidemes[i]}" ;
done

rm /tmp/whyme*
unset

exit

##

Have fun with that.

Share

BASH Examples

I have been earnestly studying BASH for a while now, but I recently came across something that will be very useful and I thought I’d pass it along.

Famously enough I run Ubuntu (a Debian-based distribution of Linux).  This is where I do my scripting in BASH, though I also use BASH on my Mac (native) and Windows (through Cygwin).

I have discovered a package called bash-doc which contains a plethora of BASH example scripts and code chunks.

To install bash-doc you just mark it for installation in Synaptic and Apply that change.

  • System —> Administration —> Synaptic Package Manager
  • Scroll down to locate bash-doc
  • Right-click on bash-doc and select Mark for Installation
  • Click the Apply button
  • Double-check the package you are installing and click OK

Alternatively you could enter this simple line of code directly into BASH via the terminal: sudo apt-get install bash-doc (your password will be required either way).

Once you have installed this package you will find a host of examples:

/usr/share/doc/bash/examples/

Have fun with that.  I know I will.

Share

Backup Script: A Love Story

I have written a few backup scripts by now. Every time I do I find a new interesting challenge somewhere in the task. As such I’d like to talk a little about my most recent backup script and offer my script to the community at large.

First of all, I tend to use rsync for backups. It’s powerful and it works well. You can use rsync to backup any file system and so it is also very flexible in a network or on a virtual machine. I’m not going to dive into the man page for rsync, but you will want to take a look there (man rsync) because there is a wealth of information about the various switches available for that command. I have selected the switches that fit my purposes and that is what is displayed below.

Next, where possible I prefer to leave my backup drives unmounted until they are actually needed for the backup process. I consider my current system for dealing with this imperfect, but again that is what you will see in my current script.

Finally, the server I have written this script for is running the desktop version of Ubuntu. This is not likely important in any way except that (as you will see) if I had been running the server version I would not likely have had the same problems when I did my test run.

Ok, so here is my current script.

##
#!/bin/bash
# Simple Back Up Script
# by JamesIsIn from JamesIsIn.com
# Do something nice today.


# run in cron Mondays at 4 AM
# [0 4 * * 1 /home/[scriptpath] >/dev/null 2>1]

# redirect from script --> sends all STDERR to log file

exec 2> /home/[username]/Desktop/$(date +%Y%m%d)
## 2> routes standard error as well as standard output together the rest uses a dated folder at the specified location



## backup [DriveA]


if ! mountpoint -q /media/[BUDriveA]
then
   mount -t [filesystem] -U [UUID goes here; no brackets or quotes] /media/[BUDriveA]
fi

# Note rsync -a copies permissions but will not copy owner:group if not run as root

rsync -ailS --delete --progress /media/[DriveA]/[Folder] /media/[BUDriveA]

umount /media/[BUDriveA]


## backup [DriveB]


if ! mountpoint -q /media/[BUDriveB]
then
   mount -t [filesystem] -U [UUID goes here; no brackets or quotes] /media/[BUDriveB]
fi

# Note rsync -a copies permissions but will not copy owner:group if not run as root

rsync -ailS --delete --progress /media/[DriveB]/[Y] /media/[BUDriveB]
rsync -ailS --delete --progress /media/[DriveB]/[Z] /media/[BUDriveB]

umount /media/[BUDriveB]

#

As you can see I like to keep my scripts well documented. I encourage you to do the same. Memory is fallible, after all.

(Note: Everything in square brackets [] will be replaced in your script.)

[filesystem]

This is where you specify the file system type you are using and is called out by the -t argument. For my script it was ext3.

[UUID]

This is a unique identifier for a drive and it is called out by the -U argument. I prefer using UUID’s because other drive/partition designations can change (for instance, sda1 can become sdb1 if you add a new drive). (For information on adding drives to your system see this post.)

My backup script manages the backups for three directories (recursive) over two drives. I first test to see if a backup drive is mounted and if it is not I mount it. That’s the job of the if statements. (This is what I consider imperfect and will seek to improve as time moves forward.) It works well enough, and if you don’t want your backup drives to be mounted all the time this is a decent way of dealing with the matter.

You will also note that I unmount each drive as I finish with it (umount).

You will also see that in the DriveB example I backup (synchronize) two directories (Y & Z). This also helps to make it clear that you do not need to specify the name of the directory at the backup location.

Lastly, you see my note about -a copying permissions and requiring root to copy also the owner and user information. As such I put this script into a cron job as root.

What?

It’s easy enough to do. Just open cron as root:

#
#
sudo crontab -e

You will be prompted for your password. You can learn more here. You can see my cron entry for this script in a comment in my script above. The man page for cron (man cron) will help you understand how 0 4 * * 1 means every Monday at four in the morning.

Let’s talk about some of the mistakes I made.

The first big snag was not having the if statements correct. I left out the UUID’s and so the script did not mount the drive for the first sync operation. The if statement tested to see if DriveA was mounted and found that it was not. Then it ran the mount command which failed because no valid drive (UUID) was specified.

Because the drive was not mounted at the mount point rsync began synchronizing data to the mount folder and not the mounted drive. This caused the script to fail (once the OS drive containing / was full—about 105 GB later) and borked the / partition.

I was not able to restart Gnome (Gnome, the desktop environment, requires some free space on your / partition to function and mine was 100% full). I ssh’d into the machine from a Windows box nearby (using Cygwin) and maybe two hours later I sorted out what I had done. I was able to remove (using the rm command) the offending folder after making absolutely certain DriveA was in fact not mounted. After that I was able to reboot and get back into Gnome. (However, I did have to run fsck on the drive probably due also to the drive having filled itself. If you boot your system and get a shell stating you cannot login try running fsck and answering y to all the fixit questions.)

Oops. This is why we make test runs, right?

So I fixed the if statements (specifically the mount and umount commands) and that took care of that.

Then the script ran fine through the backup for DriveA but finished DriveB in a few seconds. Not possible. I looked back at the script and realized that I had specified the backup location for both the source and the destination. Damn it. Fixed that and DriveB was synchronizing properly.

I hope this helps you out.

(Thanks to Ian over at the Ubuntu Forums for his suggestions while I was troubleshooting.)

Happy scripting.

Share