CodeWithStyle - Better bash scripts

in #bash6 years ago (edited)

ProgrammingWithStyle.png

After all it is such a handy language we use every day, often do not realize the power on our fingertips. I list here five tips which may save you a lot of code and improve it's quality and sexiness.
by @str84word

Handle the last slash in path

Sometimes we ask the user to enter a path for the base directory on which we either build some path tree or look for executable. In this situation we can never know either user enters slash at the end of the path or not. Either way, this is really common problem, and often you can see different workarounds. Bash lets you handle this problem with merely one line of code.

E_NOARGS=86
E_BADARG=87
USR_PATH=$1                 # => '~/go/'
USR_PATH="${USR_PATH%/}/"   # => '~/go/' (same as if the input was '~/go')
if [ -d "${USR_PATH}" ]; then
    SRC=${USR_PATH}src/     # => '~/go/src/'
    BIN=${USR_PATH}bin/     # => '~/go/bin/'
    PKG=${USR_PATH}pkg/     # => '~/go/pkg/'
    if [ -d ${SRC} ] && [ -d ${BIN} ] && [ -d ${PKG} ]; then
        echo "${USR_PATH} is a proper GOPATH repository."
    else
        exit E_BADARG
    fi
else
    exit E_NOARGS
fi
# ...

Clear the file content

You probably know that one can send data to /dev/null when the output is not significant.

mv * ./src 2> /dev/null

But did you know that the reverse (sending /dev/null output to the file) works as well ? In result we get an easy mechanism to clear logs and releasing some computation data. There is however shorter syntax for doing just that.

cat /dev/null > computation.log
: > computation.log     # Same as above
> computation.log       # Still same behavior

Check if a program exists on the $PATH

This one is fairly simple solution, yet somebody may try to reinvent the wheel and manually check the $PATH. Luckily the type command with -p argument is returning either the path string (which evaluates to true in conditional expression) or nothing (which evaluates to false).

dep=java
if ! `type -p ${dep} > /dev/null`; then
    echo "Sorry, you need ${dep} to run my app."
    exit 1;
fi

Variable type and closure

The way to extract a variable to a child process, that most programmers stick to, is the export method. Local scope is often handled by the local keyword which delimiters a variable only to the local process. Also you can merely see bash scripts which are using constants. Well, there is only one keyword you need to remember, giving a solution to all of above problems and more, it is quite like panacea for bash variable manipulation. Creating a variable simply using the declare is restricting it to the given scope, causing closure effect. Useful to remember is also: -x for export, -r (read-only) for constants, -a for array, -i for integers (behaves similar to let but with further scope restriction, see the example).

foo () { 
    local let x=10/2
    declare -i -r y=10/2    # => 5
}

access () { 
    foo
    echo $x         # => 5
    echo $y         # => (nothing)
}

Command substitution

This is powerful concept that often seems to be misunderstood. Those are kind-of anonymous blocks of code. The block evaluation output can be plugged in to a command. Command substitutions can be also assigned to a variable for later use. For an simple example, sending ls -ltr trough the command substitution will result in a nice formatted ls output echoed to stdin.

echo "$(ls -ltr)"       # You need to escape the command substitution
                        # with quote, otherwise echo will eat newlines.

The wide used syntax for creating an command substitution is trough the backticks `...`. Hover the $(...) has superseded the backticks syntax as it comes with few benefits, one of which is nesting command substitutions. Let's try actually something fun, like signing a loop to a variable.

names=( 'Alice' 'Bob' 'Charlie')

listNames=$(for i in $(seq 0 $(expr ${#names[@]} - 1)); do
    declare -i index=${i}+1
    echo -n "${index}.${names[${i}]} "
done)

names=( 'Dave' 'Erin' 'Frank')

echo "Participants: ${listNames}" # => Participants: 1.Alice 2.Bob 3.Charlie 

Command substitution is ideal tool for expending bash tool-set. You can plug outside scripts, programs written in any language, or assign a code from a file very easily.

fork_c=$(itoa 77)       # Now $fork_c will execute the c program from
                        # ./ directory. You can test it by downloading
                        # a C code snippet from:
                        # https://paste.debian.net/1041904/
                        # Then compile it to the directory where your
                        # script lives:
                        # gcc -std=c99 itoa.c -o itoa

file_definition=$(<itoa.c)  # Get content of the 'itoa' source code
self_definition=$(<$0)      # Save the content of the scripts itself

Notice from ABS Guide: "Do not set a variable to the contents of a long text file unless you have a very good reason for doing so. Do not set a variable to the contents of a binary file, even as a joke." (bit=$(<itoa) << do not dare!)

Conclusion

Order is crucial to avoid needless complexity. I hope you now say to yourself, "Wow this dude have style" and then you stick with this thought any time you write bash again but where the 'dude' will be you this time :).

Sort:  

The wide used syntax for creating an command substitution is trough the backticks ....

Your backticks aren't actually shown though, you would have to type them like so:
(2 ticks, space, 1 tick, dots, 1 tick, space, 2 ticks) to display: `...`

Yeah, that one was pretty problematic, thank you!

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Congratulations @str84word! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of upvotes received

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemitBoard - Witness Update

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @str84word! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You made your First Comment

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @str84word! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.20
TRX 0.14
JST 0.030
BTC 66937.04
ETH 3270.78
USDT 1.00
SBD 2.74