0

I am writing my first Bash script and am running into a syntax issue with a function call.

Specifically, I want to invoke my script like so:

sh myscript.sh -d=<abc>

Where <abc> is the name of a specific directory inside of a fixed parent directory (~/app/dropzone). If the child <abc> directory doesn't exist, I want the script to create it before going to that directory. If the user doesn't invoke the script with a -d argument at all, I want the script to exist with a simple usage message. Here's my best attempt at the script so far:

#!/bin/bash
dropzone="~/app/dropzone"

# If the directory the script user specified exists, overwrite dropzone value with full path
# to directory. If the directory doesn't exist, first create it. If user failed to specify
# -d=<someDirName>, exit the script with a usage statement.
validate_args() {
    args=$(getopt d: "$*")
    set -- $args
    dir=$2
    if [ "$dir" ]
    then
        if [ ! -d "${dropzone}/targets/$dir" ]
        then
            mkdir ${dropzone}/targets/$dir
        fi
        dropzone=${dropzone}/targets/$dir
    else
        usage
    fi
}

usage() {
    echo "Usage: $0" >&2
    exit 1
}

# Validate script arguments.
validate_args $1

# Go to the dropzone directory.
cd dropzone
echo "Arrived at dropzone $dropzone."

# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.

When I try running this I get the following error:

myUser@myMachine:~/app/scripts$ sh myscript.sh -dyoyo
mkdir: cannot create directory `/home/myUser/app/dropzone/targets/yoyo': No such file or directory
myscript.sh: 33: cd: can't cd to dropzone
Arrived at dropzone /home/myUser/app/dropzone/targets/yoyo.

Where am I going wrong, and is my general approach even correct? Thanks in advance!

2 Answers2

2

Move the function definitions to the top of the script (below the hash-bang). bash is objecting to the undefined (at that point) call to validate_args. usage definition should precede the definition of validate_args.

There also should be spacing in the if tests "[ " and " ]".

    if [ -d "$dropzone/targets/$1" ]

The getopt test for option d should be-:

    if [ "$(getopt d "$1")" ]

Here is a version of validate_args that works for me. I also had to change the drop zone as on my shell ~ wouldn't expand in mkdir command.

dropzone="/home/suspectus/app/dropzone"

validate_args() {
    args=$(getopt d: "$*")
    set -- $args
    dir=$2
    if [ "$dir" ]
    then
        if [ ! -d "${dropzone}/targets/$dir" ]
        then
            mkdir ${dropzone}/targets/$dir
        fi
        dropzone=${dropzone}/targets/$dir
    else
        usage
    fi
}

To pass in all args use $* as parameter -:

  validate_args $*

And finally call the script like this for getopt to parse correctly-:

  myscript.sh -d dir_name
suspectus
  • 16,548
  • 8
  • 49
  • 57
  • Thanks @suspectus (+1) - when I make all those changes (please see my edit to the original question) I now have 2 lines that are in error: `myscript.sh: 5: myscript.sh: validate_args: not found myscript.sh: 6: myscript.sh: Syntax error: "(" unexpected`. –  Apr 23 '13 at 18:08
  • Thanks again, but I have made all of your recommended changes and still getting errors. It looks like I am correctly using `getopt` but for some reason bash isn't liking me. –  Apr 23 '13 at 18:20
  • 1
    bash doesn't like me sometimes either. :) – suspectus Apr 23 '13 at 18:59
  • Thanks @suspectus (+1 again) - I applied your changes, which thankfully got rid of the `getopt` errors, but now 2 things are happening: (1) it can't create the desired directory, and (2) running the script requires me to type `-dsomeDir` for arguments instead of `-d=someDir`. Please see my updated error section in the original question...and thanks again, **enormously**!! –  Apr 23 '13 at 19:35
  • Sorry in haste I forgot to mention two salient points. see edits. – suspectus Apr 23 '13 at 22:02
1

When invoked, a function is indistinguishable from a command — so you don't use parentheses:

validate_args($1)  # Wrong
validate_args $1   # Right

Additionally, as suspectus points out in his answer, functions must be defined before they are invoked. You can see this with the script:

usage

usage()
{
    echo "Usage: $0" >&2
    exit 1
}

which will report usage: command not found assuming you don't have a command or function called usage available. Place the invocation after the function definition and it will work fine.


Your chosen interface is not the standard Unix calling convention for commands. You'd normally use:

dropzone -d subdir

rather than

dropzone -d=subdir

However, we can handle your chosen interface (but not using getopts, the built-in command interpreter, and maybe not using GNU getopt either, and certainly not using getopt as you tried to do so). Here's workable code supporting -d=subdir: #!/bin/bash

dropzone="$HOME/app/dropzone/targets"

validate_args()
{
    case "$1" in
    (-d=*)  dropzone="$dropzone/${1#-d=}"; mkdir -p $dropzone;;
    (*)     usage;;
    esac
}

usage()
{
    echo "Usage: $0 -d=dropzone" >&2
    exit 1
}

# Validate script arguments.
validate_args $1

# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."

# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.

Note the cautious approach with the cd $dropzone || exit 1; if the cd fails, you definitely do not want to continue in the wrong directory.

Using the getopts built-in command interpreter:

#!/bin/bash

dropzone="$HOME/app/dropzone/targets"

usage()
{
    echo "Usage: $0 -d dropzone" >&2
    exit 1
}

while getopts d: opt
do
    case "$opt" in
    (d) dropzone="$dropzone/$OPTARG"; mkdir -p $dropzone;;
    (*) usage;;
    esac
done
shift $(($OPTIND - 1))

# Go to the dropzone directory.
cd $dropzone || exit 1
echo "Arrived at dropzone $dropzone."

# The script will now do other stuff, now that we're in the "dropzone".
# ...etc.
Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks @Jonathan Leffler (+1) - please see my comment underneath suspectus' answer - I have the same question for you! –  Apr 23 '13 at 18:09
  • 1
    Note that the function syntax is `funcname() { ...body... }` with parentheses in the definition (and with `bash`, optionally `function funcname() { ...body... }`). You're not including the parentheses in the definition — as you need to — whereas originally you were including parentheses in the invocation — which you must not do. – Jonathan Leffler Apr 23 '13 at 18:39
  • Thanks again (+1) - I edited my question to show what happens when I add the parens to the function defs, and the resulting error messages. Basically, it gets rid of the 1st and last error message but still leaves me with 5 `getopt` errors and 2 other errors relating to `dropzone`. –  Apr 23 '13 at 18:46
  • Various comments: if the directory already exists, there's not much point in creating it again (it will fail). Using `mkdir -p "$dropzone/targets/$d"` would work unconditionally. Your use of `getopt` is unorthodox in the extreme, and the `=` notation might be handled by the GNU version of `getopt` but not by others. More comments later, if you're lucky...work is pending. – Jonathan Leffler Apr 23 '13 at 19:30