2
\$\begingroup\$

I've seen others use && for conditionally continuing execution in bash, and found it effective but rather ugly. (I'm not sure if there's a better name for this technique.)

do_something && something_else && the_next_thing

Then I found out that you can simply wrap things in parentheses just because you feel like it. (And in my original motivation was to return to easily return to the calling folder.)

So in the following script, I tried combining those techniques. (The motivation for the script is an attempt to install RabbitVCS for development.)

Questions:

  • is combining && and () in this way "idiomatic" for bash?
  • similarly, is using () to return to the calling folder appropriate
  • how's my indentation? (I normally program in Python ;) )
    #! /bin/bash

    # CC0 To the extent possible under law, the person who associated CC0
    # with this work has waived all copyright and related or neighboring
    # rights to this work. https://creativecommons.org/publicdomain/zero/1.0/

    # Required folders (must exist beforehand):
    #  * Source code and script folders (see export statements below)
    #  * Nautilus 3: ~/.local/share/nautilus/python-extensions
    #  * CLI: /usr/bin (always exists)
    #  * GEdit: ~/.local/share/gedit/plugins

    # Out of scope for this script:
    #  * Setting up folders above
    #  * Keeping RabbitVCS source code up-to-date

    # Set customisable folder paths:
    # Source code, cloned from GitHub
    export SOURCE_FOLDER=~/Code/rabbitvcs
    # User scripts location for RabbitVCS CLI (must be in .bashrc or equivalent)
    export SCRIPT_FOLDER=~/Code/scripts

    if [ "$1" = "--help" ] || [ -z $1 ] || [ "$2" ];
    then

        # if "--help" is called, or too many or too few parameters given, display help
        echo "Install specified branch of RabbitVCS with Nautilus, Gedit and CLI clients."
        echo "(Assumes prerequisites are installed and target branch and folders exist.)"
        echo "Usage: install-rabbitvcs-branch "

    else
        ( # parentheses so that we return to original folder

            # install all prerequisites, even the obvious ones
            # (package names as used in Ubuntu 13.10)
            # (also triggers prompt for sudo password as soon as possible)
            sudo apt-get install gedit git hicolor-icon-theme ipython meld subversion python python-configobj python-dbus python-dulwich python-gobject python-gobject-2 python-gtk2 python-gtkspell python-nautilus python-simplejson python-support python-svn -qq && (

                # kill existing Nautilus and RabbitVCS processes (if they exist)
                pgrep -f nautilus && nautilus -q
                pgrep -f service.py && xargs kill

                # checkout requested branch
                cd $SOURCE_FOLDER && git checkout $1 && (

                    # (re)install RabbitVCS library
                    sudo python setup.py check && sudo python setup.py install && (

                        # (re)install RabbitVCS clients
                        echo "Installing RabbitVCS clients..."
                        cp clients/nautilus-3.0/RabbitVCS.py ~/.local/share/nautilus/python-extensions
                        cp clients/gedit/rabbitvcs-plugin.py ~/.local/share/gedit/plugins
                        cp clients/gedit/rabbitvcs-gedit3.plugin ~/.local/share/gedit/plugins
                        cp clients/cli/rabbitvcs $SCRIPT_FOLDER
                        echo "done."
                    )
                )
            )
        )
    fi
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Firstly, this will most likely do a wrong thing if the first argument is empty:

if [ "$1" = "--help" ] || [ -z $1 ] || [ "$2" ];

You should probably use something like this instead (note added quotes):

if [ "$1" = "--help" ] || [ -z "$1" ] || [ $# -gt 1 ];

In general you should quote all variables which are not guaranteed to be set, to be non-empty and to only contain alphanumeric characters.

Secondly, I would not call myself a seasoned bash hacker, but parenthesis are imo more common if you need to run a small command in another directory, e.g.

(cd -P "$anotherdir"; make fun)

When it comes to longer code pieces you can either use a function or simply pushd and popd, e.g.

pushd .
cd "$anotherdir"
# do something else
popd

As for formatting, I also use four-space indentation but prefer more dense code and therefore put then on the same line as if. Additionally, I usually write following code if I need several commands to run in sequence only if everything worked out fine:

stderr() {
    cat - 1>&2
}

fail() {
    printf "%s\n" "$@" | stderr
    return 1
}

commandA "$arg1" "$arg2" || fail "ERROR: something went wrong"
commandB "$arg1" "$arg2" || fail "ERROR: something else went wrong"

UPDATE: Incorporate rolfl's suggestion to use a fail() function.

\$\endgroup\$
3
\$\begingroup\$

The use of the parenthesis like you have it, is not 'idiomatic' in Bash.

Each time you use it you are creating a sub shell, and that incurs an overhead that is typically avoided.

Also, all those cp commands in the inner-block .... if one of them fails, you will just keep going, so your handling is incomplete anyway....

Bash error handling can become cumbersome. I recommend reading some decent articles, and looking in to traps and signals....

Also, consider adding your error handling as a simple function...

In other notes....

Your test conditions are odd..... These lines:

if [ "$1" = "--help" ] || [ -z $1 ] || [ "$2" ];
then

would be better written as

if [[ "$1" = "--help" || -z "$1" || -n "$2" ]]; then

See these details on the \[\[ ... \]\] test and the Bash conditionals IBM has a decent document describing the different test and conditional structures in Bash.

Note that I put the then on the same line as the if. This is because you have the ; semi-colon on the if line, and the only reason to have the semi-colon is to allow the then to be on the same line.... It is standard in bash to put the then on the same line as the if (see the documentation....).

\$\endgroup\$
3
  • \$\begingroup\$ I don't see why the classic test command ("[" and "]") should be replaced with the Bash one ("[[" and "]]"). Did you do this for a reason, or out of preference? \$\endgroup\$
    – mkalkov
    Commented Mar 12, 2014 at 14:58
  • \$\begingroup\$ Did it for a reason... the [[ ... || ... || ... ]] is a single test operation embedded in bash, whereas the [ ...] || [ ... ] || [ ... ] are three tests (potentially) using the external command [ (type man test on your commandline). Also, the [[ is more versatile. In general I feel it is more bash-like (out of interest, type [ --help on your commandline ;-) ) \$\endgroup\$
    – rolfl
    Commented Mar 12, 2014 at 15:11
  • \$\begingroup\$ Ok, now I understand why. However, I prefer [ because I usually run scripts via dash which starts quicker than bash and has a built-in test command. I'm also trying to learn all features of POSIX shell before diving into bash, which is another reason. \$\endgroup\$
    – mkalkov
    Commented Mar 12, 2014 at 15:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.