Bash notes

From raju

Scripts

Check number of arguments

$cat check_arg_num.sh
#! /usr/bin/env bash
set -e
set -u
if [ "$#" -ne 1 ]; then
  echo "Usage: $0 foo" >&2
  exit 1
fi
foo=$1
echo $foo

Sample run without an argument

$./check_arg_num.sh
Usage: ./check_arg_num.sh foo

Sample run with an argument

$./check_arg_num.sh something
something

full name, file name, file head, extension

for fullname in /path/to/*.csv
do
fname="${fullname##*/}"
fhead="${fname%.*}"
ext="${fname##*.}"
echo $fullname $fname $fhead $ext
done

Ref:- https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash

replace string

$a="space_kama_raju_kama_raju_kama_raju"

$echo $a
space_kama_raju_kama_raju_kama_raju

$echo ${a/kama/foo}
space_foo_raju_kama_raju_kama_raju


$echo ${a//kama/foo}
space_foo_raju_foo_raju_foo_raju

looping on the command line

for i in foo.csv bar.csv; do echo "==>" $i "<=="; cat $i; echo ; done

default arguments

The idea here is to have some default values assigned to a variable if nothing is specified on the command line. If some values are specified on the command line, they will be used instead.

$cat default_args.sh
#! /usr/bin/env bash

if [ "$#" -eq 0 ]; then
    dirs=(
        "path1"
        "path2"
        )
else
    dirs="$@"
fi

for d in ${dirs[@]}
do
    echo $d
done

$./default_args.sh
path1
path2

$./default_args.sh foo bar
foo
bar

Used in git_status.sh to get the list of directories on which we want to run "git status" type of commands.

Demonstrates | check number of arguments in a bash script

code snippets

check if a variable is empty

if [ -z $BASE ]; then
    # do something
fi

check for two conditions inside if

This checks if BASE variable is empty or if LOCAL and BASE are the same.

if [ -z $BASE ] || [ $LOCAL = $BASE ]; then
    # do something
fi

compare numbers

Sample code

# --autostash is available only if git version is >= 2.6
git_version=`git --version | cut -f3 -d' '`
major_version=`echo $git_version | cut -f1 -d.`
minor_version=`echo $git_version | cut -f2 -d.`

if (( $major_version >= 2 )) && (( $minor_version >= 6 )); then
        use_autostash=1
else
        use_autostash=0
fi

if [ $use_autostash == 1 ]; then
        git pull --rebase --autostash -v origin
else
    git stash
    git pull --rebase -v origin
    git stash pop
fi

To compare floating point numbers

$ [[ 2.5 > 2.10 ]] && echo 1 || echo 0
1
$ [[ 2.5 < 2.10 ]] && echo 1 || echo 0
0

It also works for comparing integers.

$ [[ 3 > 2 ]] && echo 1 || echo 0
1
$ [[ 3 < 2 ]] && echo 1 || echo 0
0

-gt, -lt etc., will not work for floating point numbers

$ [[ 2.5 -gt 2.10 ]] && echo 1 || echo 0
bash: [[: 2.5: syntax error: invalid arithmetic operator (error token is ".5")
0

$ [[ 2.5 -lt 2.10 ]] && echo 1 || echo 0
bash: [[: 2.5: syntax error: invalid arithmetic operator (error token is ".5")
0

But they work for integers

$ [[ 3 -gt 2 ]] && echo 1 || echo 0
1
$ [[ 3 -lt 2 ]] && echo 1 || echo 0
0

tested with

$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)

$ cat /etc/debian_version
9.4

sort after

ISO-8601 compliant dates

$ date +%Y-%m-%d
2017-10-30
$ date +%Y-%m-%dT%H:%M:%S%z
2017-10-30T17:44:13-0400

You can also achieve this by -I

$ date -I
2017-10-30

$ date -Iminutes
2017-10-30T17:47-04:00

$ date -Iseconds
2017-10-30T17:47:39-04:00

Equivalence between -Iseconds and -u

$date +%FT%T%z
2017-10-30T17:53:06-0400

$date -u +%FT%TZ
2017-10-30T21:53:06Z

Basically UTC time + offset printed by %z is equal to the local time.

Ref:-

redirect both stdout and stderr to a file

foo.sh > out.txt 2>&1

To append both stdout and stderr to a file

foo.sh >> out.txt 2>&1

controlling history

Add the following to ~/.bashrc

shopt -s histappend

# prevent duplicate entries of a single session from being saved to $HISTFILE.
export HISTCONTROL=ignoreboth:erasedups
export HISTSIZE=20000

# Up arrow will show commands from other shells as well
export PROMPT_COMMAND="history -a;history -c;history -r;$PROMPT_COMMAND"

# When a command is duplicated, store only the latest
# Adapted from https://unix.stackexchange.com/questions/48713/how-can-i-remove-duplicates-in-my-bash-history-preserving-order
tac $HISTFILE | awk '!x[$0]++' | tac > /tmp/tmpfile && "mv" -fv /tmp/tmpfile $HISTFILE
wc -l $HISTFILE

What they do

* ignoreboth - does both ignorespace and ignoredups
* erasedups - 
* ignorespace - ignore commands that start with spaces
* ignoredups - ignore duplicates
* history -a  # append history lines from this session to the history file.
   
History file may contain history from other terminals not in this one so:

* history -c # clear [in-memory] history list deleting all of the entries.
* history -r # read the history file and append the contents to the history list instead.

Missing Features

  1. Currently, it is not possible to list files by modification time using tab completion. Use zsh instead.

Ref:- http://lists.gnu.org/archive/html/bug-bash/2014-12/msg00161.html
http://www.zsh.org/mla/users/2015/msg00078.html

Useful links