Monday, July 23, 2012

mazes: just for fun

Here is a small screencast showing some randomly generated mazes. I slightly modified the code I posted on my previous post to show the solution path step by step as it is created.

Friday, July 20, 2012

python: maze generation and solution

Here I'm posting a maze generation and resolve class written in python. I wanted to learn python and generating and solving mazes is a good exercise to start with.
In addition to the maze class I've written another script using pygame to show the maze and its solution path in a window.
This is my first try with python, there is for sure a best way of doing things. Please feel free to comment and suggest!

The generation algorithm creates a "Perfect" maze. This means that, assuming I wrote it correctly, for every pair of cells chosen there is always exactly one path connecting them. No loops and no isolated regions.
There are many ways for solving mazes, I choose a "Shortest Path Finder" here.

This is a very good website with tons of information about maze-types, generation algorithms, solution algorithms, etc.

Maze Class: Crate, solve and display on terminal

mymazeclass.py
  1 #=============================================================================#
  2 # Name        : mymazeclass.py                                                #
  3 # Autor       : Adrian Antonana                                               #
  4 # Date        : July, 20 2012                                                 #
  5 # Description : Maze class. Constructor generates random perfect maze. Maze   #
  6 #               can also be solved.                                           #
  7 #=============================================================================#
  8 
  9 
 10 # imports
 11 import random
 12 import sys
 13 
 14 
 15 # constants and help for list access
 16 BOTTOMWALL = 0
 17 RIGHTWALL = 1
 18 VISITED = 2
 19 UP = 0
 20 RIGHT = 1
 21 DOWN = 2
 22 LEFT = 3
 23 
 24 
 25 # maze class definition
 26 class Maze:
 27 
 28   #object constructor
 29   def __init__(self,rows,cols):
 30   
 31     # set maze size, walls and visited values
 32     self.rows = rows
 33     self.cols = cols
 34     self.maze = [[[True,True,False] for j in range(cols)] for i in range(rows)]
 35 
 36     # set current position, start and end point
 37     # start and end points are used to solve the maze
 38     # currrow is used to generate the maze
 39     self.startrow = random.randrange(rows)
 40     self.startcol = random.randrange(cols)
 41 
 42     self.endrow = random.randrange(rows)
 43     self.endcol = random.randrange(cols)
 44 
 45     currrow = self.startrow
 46     currcol = self.startcol
 47 
 48     # The searh can be quite deep
 49     if rows*cols > sys.getrecursionlimit():
 50       sys.setrecursionlimit(rows*cols+10)
 51 
 52     # generate the maze with depth-first algorithm
 53     self._gen_maze(currrow,currcol)
 54 
 55     # number matrix for solving
 56     self.numtable = [[-1 for j in range(cols)]for i in range(rows)]
 57 
 58     #solution path
 59     self.solutionpath = []
 60 
 61   #-----------------------------------------------------------------------------
 62 
 63   # returns the maze in ascii characters for printing on terminal
 64   def __str__(self):
 65 
 66     # the upper wall first
 67     outtable = '.'+self.cols*'_.'+'\n'
 68 
 69     for i in range(self.rows):
 70       outtable += '|'
 71 
 72       for j in range(self.cols):
 73         if self.maze[i][j][BOTTOMWALL]:
 74           outtable += '_'
 75         else:
 76           outtable += ' '
 77         if self.maze[i][j][RIGHTWALL]:
 78           outtable += '|'
 79         else:
 80           outtable += '.'
 81 
 82       outtable += '\n'
 83 
 84     outtable += 'Start Point   : ('+str(self.startrow)+','+str(self.startcol)+')\n'
 85     outtable += 'End Point     : ('+str(self.endrow)+','+str(self.endcol)+')\n'
 86 
 87     return outtable
 88 
 89   #------------------------------------------------------------------------------
 90 
 91   # get a list with posible directions from the current position
 92   def _get_dirs(self,r,c):
 93     dirlist = []
 94 
 95     # check limits
 96     if r-1 >= 0           : dirlist.append(UP)
 97     if r+1 <= self.rows-1 : dirlist.append(DOWN)
 98     if c-1 >= 0           : dirlist.append(LEFT)
 99     if c+1 <= self.cols-1 : dirlist.append(RIGHT)
100 
101     return dirlist
102 
103   #------------------------------------------------------------------------------
104 
105   # generates the maze with depth-first algorithm
106   def _gen_maze(self,r,c,d=None):
107     maze = self.maze
108 
109     # knock down the wall between actual and previous position
110     maze[r][c][VISITED] = True
111     if   d == UP    : maze[r]  [c]    [BOTTOMWALL] = False
112     elif d == DOWN  : maze[r-1][c]    [BOTTOMWALL] = False
113     elif d == RIGHT : maze[r]  [c-1]  [RIGHTWALL]  = False
114     elif d == LEFT  : maze[r]  [c]    [RIGHTWALL]  = False
115 
116     # get the next no visited directions to move
117     dirs = self._get_dirs(r,c)
118 
119     # random reorder directions
120     for i in range(len(dirs)):
121       j = random.randrange(len(dirs))
122       dirs[i],dirs[j] = dirs[j],dirs[i]
123 
124     # make recursive call if the target cell is not visited
125     for d in dirs:
126       if d==UP:
127         if not maze[r-1][c][VISITED]:
128           self._gen_maze( r-1,c,UP )
129       elif d==DOWN:
130         if not maze[r+1][c][VISITED]:
131           self._gen_maze( r+1,c,DOWN )
132       elif d==RIGHT:
133         if not maze[r][c+1][VISITED]:
134           self._gen_maze( r,c+1,RIGHT )
135       elif d==LEFT:
136         if not maze[r][c-1][VISITED]:
137           self._gen_maze( r,c-1,LEFT )
138 
139   #------------------------------------------------------------------------------
140 
141   # solve the maze by filling it with numbers(algorithm name?)
142   def _solve_maze_aux(self,r,c,n):
143     maze = self.maze
144     numtable = self.numtable
145     numtable[r][c] = n
146 
147     # check if the end has been reached
148     if (r,c) != (self.endrow,self.endcol):
149       directions = self._get_dirs(r,c)
150 
151       # recursive calls only if there is no wall between cells and
152       # targel cell is not marked (=-1)
153       for d in directions:
154         if   d==UP    and not maze[r-1][c][BOTTOMWALL] and numtable[r-1][c] == -1:
155           self._solve_maze_aux(r-1,c,n+1)
156         elif d==DOWN  and not maze[r][c][BOTTOMWALL]   and numtable[r+1][c] == -1:
157           self._solve_maze_aux(r+1,c,n+1)
158         elif d==RIGHT and not maze[r][c][RIGHTWALL]    and numtable[r][c+1] == -1:
159           self._solve_maze_aux(r,c+1,n+1)
160         elif d==LEFT  and not maze[r][c-1][RIGHTWALL]  and numtable[r][c-1] == -1:
161           self._solve_maze_aux(r,c-1,n+1)
162 
163   #------------------------------------------------------------------------------
164 
165   # get the solution path
166   def _get_solution_path(self):
167     actrow = self.endrow
168     actcol = self.endcol
169     startrow = self.startrow
170     startcol = self.startcol
171     path = []
172     numtable = self.numtable
173     path = self.solutionpath
174 
175     while (actrow,actcol) != (startrow,startcol):
176       path.append((actrow,actcol))
177       directions = self._get_dirs(actrow,actcol)
178       for d in directions:
179         if d== UP:
180           if numtable[actrow][actcol]-1 == numtable[actrow-1][actcol]:
181             actrow -=1
182             break
183         elif d== DOWN:
184           if numtable[actrow][actcol]-1 == numtable[actrow+1][actcol]:
185             actrow += 1
186             break
187         elif d== LEFT:
188           if numtable[actrow][actcol]-1 == numtable[actrow][actcol-1]:
189             actcol -= 1
190             break
191         elif d== RIGHT:
192           if numtable[actrow][actcol]-1 == numtable[actrow][actcol+1]:
193             actcol += 1
194             break
195 
196     path.append((actrow,actcol))
197     path.reverse()
198 
199   #------------------------------------------------------------------------------
200 
201   # solve the maze
202   def solve_maze(self):
203     self._solve_maze_aux(self.startrow,self.startcol,0)
204     self._get_solution_path()

Pygame: Show maze

It is possible to print the maze on the terminal with the above class but it doesn't look very good. So I defined a few functions using pygame to show mazes properly including the solution path.

mazeprogram.py
  1 #!/usr/bin/python2
  2 
  3 
  4 #=============================================================================#
  5 # Name        : mazeprogram.py                                                #
  6 # Autor       : Adrian Antonana                                               #
  7 # Date        : July, 20 2012                                                 #
  8 # Description : Function definitions for drawing a maze in a window using     #
  9 #               pygame and the maze class (mymazeclass.py) I made.            #
 10 #=============================================================================#
 11 
 12 # imports
 13 from mymazeclass import *
 14 from sys import *
 15 import pygame
 16 
 17 
 18 #-------------------------------------------------------------------------------------#
 19 #              function definitions for drawing the maze with pygame                  #
 20 #-------------------------------------------------------------------------------------#
 21 
 22 # generates a window with maze with all cells isolated from each other
 23 def base_window(m):
 24   winwidth = m.cols*CELLSIZE+(m.cols+1)*WALLSIZE
 25   winheight = m.rows*CELLSIZE+(m.rows+1)*WALLSIZE
 26   w = pygame.display.set_mode((winwidth,winheight))
 27   w.fill(BLACK)
 28 
 29   for i in range(m.rows):
 30     for j in range(m.cols):
 31       pygame.draw.rect(w,WHITE,(WALLSIZE+(j*CELLSIZE+j*WALLSIZE),WALLSIZE+(i*CELLSIZE+
 32       i*WALLSIZE),CELLSIZE,CELLSIZE))
 33 
 34   return w
 35 
 36 #--------------------------------------------------------------------------------------
 37 
 38 # knocks down walls from base_window to create the path
 39 def maze_window(m):
 40   w = base_window(m)
 41 
 42   for i in range(m.rows):
 43     for j in range(m.cols):
 44       if not m.maze[i][j][BOTTOMWALL]:
 45         pygame.draw.rect(w,WHITE,(j*CELLSIZE+(j+1)*WALLSIZE,(i+1)*CELLSIZE+(i+1)
 46         *WALLSIZE,CELLSIZE,WALLSIZE))
 47       if not m.maze[i][j][RIGHTWALL]:
 48         pygame.draw.rect(w,WHITE,((j+1)*CELLSIZE+(j+1)*WALLSIZE,i*CELLSIZE+(i+1)
 49         *WALLSIZE,WALLSIZE,CELLSIZE))
 50 
 51   pygame.display.update()
 52   return w
 53   
 54 #--------------------------------------------------------------------------------------
 55 
 56 # paints the solution path in the maze window
 57 def maze_path_window(m,w):
 58   path = m.solutionpath
 59 
 60   # print every cell within the solution path
 61   for index in range(len(path)-1):
 62     actrow = path[index][0]
 63     actcol = path[index][1]
 64     nextrow = path[index+1][0]
 65     nextcol = path[index+1][1]
 66     pygame.draw.rect(w,RED,(actcol*CELLSIZE+(actcol+1)*WALLSIZE,actrow*CELLSIZE+(actrow+
 67     1)*WALLSIZE,CELLSIZE,CELLSIZE))
 68 
 69     # also paint the white spaces between the cells
 70     if actrow == nextrow:
 71       if actcol < nextcol:
 72         pygame.draw.rect(w,RED,((actcol+1)*CELLSIZE+(actcol+1)*WALLSIZE,actrow*CELLSIZE+
 73         (actrow+1)*WALLSIZE,WALLSIZE,CELLSIZE))
 74       else:
 75         pygame.draw.rect(w,RED,(actcol*CELLSIZE+actcol*WALLSIZE,actrow*CELLSIZE+(actrow+
 76         1)*WALLSIZE,WALLSIZE,CELLSIZE))
 77     elif actcol == nextcol:
 78       if actrow < nextrow:
 79         pygame.draw.rect(w,RED,(actcol*CELLSIZE+(actcol+1)*WALLSIZE,(actrow+1)*CELLSIZE+
 80         (actrow+1)*WALLSIZE,CELLSIZE,WALLSIZE))
 81       else:
 82         pygame.draw.rect(w,RED,(actcol*CELLSIZE+(actcol+1)*WALLSIZE,actrow*CELLSIZE+
 83         actrow*WALLSIZE,CELLSIZE,WALLSIZE))
 84 
 85 
 86   # add a different color for start and end cells
 87   startrow = path[0][0]
 88   startcol = path[0][1]
 89   endrow = path[-1][0]
 90   endcol = path[-1][1]
 91 
 92   pygame.draw.rect(w,BLUE,(startcol*CELLSIZE+(startcol+1)*WALLSIZE,startrow*CELLSIZE+(
 93   startrow+1)*WALLSIZE,CELLSIZE,CELLSIZE))
 94   pygame.draw.rect(w,GREEN,(endcol*CELLSIZE+(endcol+1)*WALLSIZE,endrow*CELLSIZE+(endrow+
 95   1)*WALLSIZE,CELLSIZE,CELLSIZE))
 96   pygame.display.update()
 97   
 98 
 99 #================================================================================#
100 #                                Main Program                                    #
101 #================================================================================#
102 
103 # sizes and colors
104 CELLSIZE = 5
105 WALLSIZE = 2
106 
107 WHITE = (255,255,255)
108 BLACK = (0,0,0)
109 RED = (255,0,0)
110 GREEN = (0,255,0)
111 BLUE = (0,0,255)
112 
113 # get the maze size from program arguments
114 rows = int(argv[1])
115 cols = int(argv[2])
116 
117 # generate random maze, solve it
118 maze = Maze(rows,cols)
119 print maze
120 
121 maze.solve_maze()
122 #print maze
123 #print maze.solutionpath
124 
125 # show the maze with the solution path
126 pygame.init()
127 window = maze_window(maze)
128 maze_path_window(maze,window)
129 
130 while True:
131   for event in pygame.event.get():
132     if event.type == pygame.QUIT:
133       sys.exit()

Example Screenshot

Running "mazeprogram.py" an the shell as follows:
    $ ./mazeprogram.py 40 40

Generate + Solve Time

A graphic comparing maze size with generation + resolution time.

Nice Vs Ugly Code

In my first try I wrote some code without really thinking about what data structures I needed and how exactly I wanted to solve the maze generation and solution problems. As a result the "ugly" code is extremely slow when maze size increases. Here is a comparison between both codes execution times.
Good Code Vs Bad Code comparison:
Take care of your code!!!

Saturday, June 2, 2012

Sed Back References: Defining Regions

Sed has the ability to define regions in the regular expression definition area. This regions can be then back referenced using the special characters "\1" to "\9". To define a region it has to be surrounded with parentheses.

As an example, lets say you have a nine characters long line that has to be splitted into three three-character regions:


Note I'm passing the "-r" argument to sed, otherwise I would have to escape the parenthesis characters to prevent sed to take them as literals.

Following is a "real life" example. I have a plain text file with email addresses and contact names with this format:
name_1:last_name_1:mail_1@domain.com
name_2:last_name_2:mail_2@domain.com
name_3:last_name_3:mail_3@domain.com
name_4:last_name_4:mail_4@domain.com
This could be difficult to read as the file grows. With just one line using sed defining regions, the output of a cat command can be formatted in a "human readable" format:


Thunar Custom Actions: Resize and Rotate Images with Convert

Here I'm posting two simple bash scripts I use along with Thunar Custom Actions to resize and rotate images. Both of them work for on or more selected files.
Rotated images will be changed in place, resizing images will keep the original file and prepend "resized_to_<percent>" to the resized image, where <percent> indicates the selected reduction percent applied.

resize script:
res.sh
     1  #!/bin/bash
     2  
     3  PERCENT="$1"
     4    shift
     5  while (( "$#" )); do  
     6    convert "$1" -resize "$PERCENT%" -quality 100 "resized_to_${PERCENT}%_$1"
     7    shift
     8  done
     9  
    10  exit 0

rotate script:
rot.sh
     1  #!/bin/bash
     2  
     3  
     4  DIRECTION="$1"
     5  shift
     6  
     7  case "$DIRECTION" in
     8    "r")
     9      while (( "$#" )); do
    10        convert -rotate 90 -quality 100 "$1" "$1" 
    11        shift
    12      done
    13    ;;
    14    "l")
    15      while (( "$#" )); do
    16        convert -rotate -90 -quality 100 "$1" "$1"
    17        shift
    18      done
    19    ;;
    20  esac
    21  
    22  exit 0

Adding Custom Actions

Take a look at this thunar wiki article it describes how to add custom actions to Thunar.
The resize script will need the desired resizing percent as an argument.
The rotate script needs the desired rotate direction (l or r), it always rotates in 90° steps.

Openbox Window Manager: Oblogout

I'm using Oblogout to shutdown, restart, etc. from Openbox. It looks very nice and it's easy to configure and customize.

To configure it you will have to edit the "oblogout.conf" file. In Archlinux this file is placed in the "/etc" directory.

To shutdown and restart the system  i'm using the good old "shutdown" command, "xlock" to lock the screen and "openbox --exit" to log out.

out.sh
     1  [settings]
     2  usehal = false
     3  
     4  [looks]
     5  opacity = 70
     6  bgcolor = black
     7  buttontheme = oxygen
     8  buttons = cancel, logout, restart, shutdown, lock
     9  
    10  [shortcuts]
    11  cancel = Escape
    12  shutdown = S
    13  restart = R
    14  logout = L
    15  lock = K
    16  
    17  [commands]
    18  shutdown = sudo shutdown -h now
    19  restart = sudo shutdown -r now
    20  lock = xlock &
    21  logout = openbox --exit

To be able to restart/shutdown without root privileges I added following lines at the end of the "/etc/sudoers" file.
Remember to edit the "sudoers" file using "visudo"

out.sh
    92  %power ALL= (ALL) NOPASSWD: /sbin/shutdown
    93  %power ALL= (ALL) NOPASSWD: /sbin/reboot
    94  %power ALL= (ALL) NOPASSWD: /sbin/halt

oblogout screenshot:


Monday, May 21, 2012

Sub Directories Tree Script: Sed

When working on the shell sooner or later one realizes that "sed" is a basic tool that although it seems to be a bit confusing at first glance, if you learn how to use it, on the long run will help you a lot.

The best way to learn how to use sed for me is by examples. So here is a script that shows all sub directories in a path with a tree-like look.

It's based on a script by Dem Pilafian I found somewhere on the Internet.

The script shows the sub directories tree on the current path if no argument is provided or the sub directories tree of the provided path.

Usage: ./lstree.sh [<path>]
Script code:
out.sh
     1  #!/bin/bash
     2  
     3  # if path is provided then list directories in path
     4  if [ ! -z "$1" ] && [ ! -d "$1" ]; then
     5    echo "$1: directory not found"
     6    exit 1
     7  elif [ ! -z "$1" ]; then
     8    cd "$1"
     9  fi
    10  
    11  # show actions
    12  pwd
    13  echo '   |'
    14  ls -R | grep ':$' | sed -r 's/:$//' | sed -r 's/[^/]*\//---/g' | sed -r 's/(-*)-/   |\1>/' | 
                                                                                    sed '/\./d'
    15  
    16  # check if no folders
    17  if [ `ls -F -1 | grep "/" | wc -l` = 0 ]
    18    then echo "-> no sub-directories"
    19  fi
    20  
    21  exit 0
Sample output:

Line 14 is the line that does all the "magic":

  • sed #1: removes the trailing colons
  • sed #2: replaces sub directories with "---"
  • sed #3: replaces the first and the last "-" with "|" and ">" respectively
  • sed #4: removes the "." line (current directory)

Sometimes it is hard for me to figure out what is the regular expression defined in a sed command actually matching. 
Lets take the 2nd sed and pipe a fake path to it:


All regular expression matches have been replaced because of the "g" at the end of the sed command. Now, replacing the "g" with numbers starting from 1 to N, one can see what are the single substitutions that have been made:



That's a simple way to check what the regular expression defined in the sed command is actually matching.

As I said at the beginning of this post, spend some time learning sed!


Wednesday, May 16, 2012

Painless Backup with Bash Script

This is a bash script I made to make my files backup process a little bit easier. The script has four basic options:
  1. Show usage
  2. Create backup
  3. Save the backup on my backup drive on remote machine
  4. Retrieve backup from remote machine

The script is written to backup the files and directories I want in my computer but it should be pretty easy to configure it to suit your needs.

I don't want this post to be too long so I'm not posting here the whole text. If you want to download the whole script, here is the link.

Run the script with the "b" option to see a brief description of usage, options and actions.
Here are just a few comments about how it works and how to modify it to make you own backups.


Configuration

Lines 23-52 determine what files and directories to backup, what programs to use, etc. Modify these lines to suit your needs.
conf.sh
    23  DATE=$(date --rfc-3339=date)
    24  BASE="$HOME"
    25  BASE_SYS="/etc"
    26  HOME_CONF_DIR=".config"
    27  BACKUP_DIR="backup"
    28  SYNC_DIR="sync"
    29  DOCS_DIR="documents"
    30  DOWN_DIR="downloads"
    31  PACK_DIR="packages"
    32  PICS_DIR="pictures"
    33  SCRIPTS_DIR="scripts"
    34  VIDS_DIR="videos"
    35  LOCAL_HOST="$(uname -n)"
    36  BACKUP_HOST="linuxmachine"
    37  BACKUP_HOST_BASE_DIR="/mnt/backup"
    38  TAR_OPTS="cf"
    39  SYNC_OPTS="-avz --human-readable --progress"
    40  BZIP_OPTS="-9 -v -f"
    41  MIN_PARM_NUM=1
    42  MAX_PARM_NUM=2
    43  PROGS_LIST="bzip2 rsync"
    44  
    45  # options
    46  VALID_MODES=(b s r h)
    47  VALID_ACTIONS=(home documents downloads packages pictures scripts videos conffileshome 
                      conffilessys conffilesall full)
    48  
    49  # What will be backed up. Files and directories
    50  CONF_DIRS_HOME=$(echo "$HOME_CONF_DIR/"{openbox,tint2,conky})
    51  CONF_FILES_HOME=".Xresources .xinitrc"
    52  CONF_FILES_SYS=$(echo "$BASE_SYS/"{mkinitcpio.conf,inittab,fstab,vimrc,rc.conf,bash.bashrc,
                       modprobe.d/modprobe.conf})

Backing up

Making a "full" backup which includes home directories, user's configuration files and system wide configuration files:

Sync with remote host

 Retrieving backup from remote host

Main part description

Basically the script works as follows:
  1. parameter check
  2. options check
  3. check programs
  4. perform requested action
These steps correspond to the last lines on the script:

main.sh
   254  #####################################################################
   255  ########################## parameter checks #########################
   256  #####################################################################
   257  
   258  # check parameters and programs
   259  check_parm_num $# $MIN_PARM_NUM $MAX_PARM_NUM
   260  [[ $? != 0 ]] && usage
   261  check_valid_parm $1 $2
   262  [[ $? != 0 ]] && usage
   263  check_programs "$PROGS_LIST"
   264  
   265  #####################################################################
   266  ############################# main part #############################
   267  #####################################################################
   268  
   269  
   270  # builds the list of actions to be done (only needed when backing up)
   271  [[ "$1" == "b" ]] && build_action_list "$2"
   272  
   273  # perform requested actions
   274  case "$1" in
   275    "b")
   276      # create backup directory for current date and localhost
   277      [[ -d "$BASE/$BACKUP_DIR/$LOCAL_HOST/$DATE" ]] || mkdir -p 
   "$BASE/$BACKUP_DIR/$LOCAL_HOST/$DATE"
   278      cd "$BASE/$BACKUP_DIR/$LOCAL_HOST/$DATE"
   279      for action in $ACTION_LIST; do backup "$action"; done
   280    ;;
   281    "s")
   282      sync "$BACKUP_HOST" "$BACKUP_HOST_BASE_DIR" "$LOCAL_HOST"
   283    ;;
   284    "r")
   285      retrieve "$BACKUP_HOST" "$BACKUP_HOST_BASE_DIR" "$BACKUP_DIR" "$LOCAL_HOST"
   286    ;;
   287    "h")
   288      usage
   289    ;;
   290  esac

I tried to keep the main part as small and clear as possible by using functions. This functions are commented including parameters that they need and what they do.
For instance here is the "build_action_list":

build_action_list.sh
    59  #builds a list of "actions" (what is going to be backed up)
    60  # $1: action
    61  function build_action_list () {
    62    case "$1" in
    63      "full")
    64        ACTION_LIST="documents downloads packages pictures scripts videos conffileshome 
                          conffilessys" 
    65      ;;
    66      "home")
    67        ACTION_LIST="documents downloads packages pictures scripts videos"
    68      ;;
    69      "conffilesall")
    70        ACTION_LIST="conffileshome conffilessys"
    71      ;;
    72      *)
    73        ACTION_LIST="$1"
    74      ;;
    75    esac
    76    
    77    return 0
    78  }

Any comments or questions?
Hope this will make your backing up a little bit easier.