Skip to content

Instantly share code, notes, and snippets.

@herbertjones
Created February 1, 2019 04:45
Show Gist options
  • Save herbertjones/2606fbf1bf238986d01247b46a441d50 to your computer and use it in GitHub Desktop.
Save herbertjones/2606fbf1bf238986d01247b46a441d50 to your computer and use it in GitHub Desktop.
Bash notes

Language Features

Variables and newlines

# Normal assignment doesn't work.
lines=$(printf "abc\n123\n\n"); echo -n "$lines" | wc -l
# 1  -- WRONG
empty=$(printf ""); echo -n "$empty" | wc -l
# 0  -- WRONG

# Problem is that variable assignment strips all newlines when assigning the
# variable.

# Solution is to not assign the variable that way.
IFS= read -rd '' lines < <(printf "abc\n123\n");  echo -n "$lines" | wc -l
# 2  -- CORRECT
IFS= read -rd '' empty < <(printf "");  echo -n "$empty" | wc -l
# 0  -- CORRECT

# echo -n or printf should be used, as "<<<" adds a newline.
IFS= read -rd '' lines < <(printf "abc\n123\n");  wc -l <<< "$lines"
 # 3  -- WRONG
IFS= read -rd '' empty < <(printf "");  wc -l <<< "$empty"
 # 1  -- WRONG

Variables and newlines and exit status

Warning: coproc seems to only exist and work correctly when the command expects and waits for input or output.

coproc printf "abc\n123\n"
IFS= read -rd '' -u "${COPROC[0]}" lines
if ! wait -n $COPROC_PID; then
    echo failure
else
    echo success
    echo -n "${lines}"
    echo
    echo -n "${lines}" | wc -l
fi

String manipulation

Length

s=0123456789
echo ${#s}  # 10

Substring

Uses formats:

${VAR:START_IDX}

${VAR:START_IDX:LENGTH}

${VAR:START_IDX:-LEN_FROM_END}

s=0123456789
echo ${s:0}     # 0123456789
echo ${s:0:-1}  # 012345678
echo ${s:0:0}   #
echo ${s:0:1}   # 0
echo ${s:1}     # 12345678
echo ${s:1:1}   # 1

Replace

Single

new_var="${some_var/FIELD/${field}}"

Multiple

new_var="${some_var//FIELD/${field}}"

s="abd
def
kje
kjer
"

echo "${s//
/ }"
# abd def kje kjer

Trim from beginning

s=test.service
echo ${s#test}  # prints ".service"
s=1test.service
echo ${s#test}  # prints "1test.service"

Trim from end

s=test.service
echo ${s%.service}  # prints "test"
s=test.service1
echo ${s%.service}  # prints "test.service"

Here Documents

Pipe to stdin of command

cat <<'EOF'
This will be displayed
via the cat
program.
EOF

Don’t quote the delimiter word if variable expansion, command substitution or arithmetic expansion is desired.

cat <<EOF
Hello.  I am $(whoami).  Nice to meet you!
There are $((60*60*24)) seconds in a day!
EOF

Can also suppress TABs. Doesn’t work on spaces. :(

cat <<-EOF
	This will be displayed
	via the cat

	program.
EOF

Or output to a file.

cat <<'EOF' > /tmp/outfile
This will be written
 to /tmp/outfile.
EOF

Variable from here document

Same rules apply as in previous section.

IFS='' read -r -d '' VARNAME <<'EOF'
This will go into VARNAME.
EOF

Multi-line comment

: <<'COMMENTBLOCK'
echo "This line will not echo."
This is a comment line missing the "#" prefix.
This is another comment line missing the "#" prefix.

&*@!!++=
The above line will cause no error message,
because the Bash interpreter will ignore it.
COMMENTBLOCK

Types

Array

Splice

declare -a arr=(1 2 3 4 5)
printf '%s ' "${arr[@]:1}"  # 2 3 4 5
printf '%s ' "${arr[@]:1:2}"  # 2 3
printf '%s ' "${arr[@]:1:1}"  # 2
printf '%s ' "${arr[@]: -2}"  # 4 5
printf '%s ' "${arr[@]: -3:1}"  # 3

Length

echo ${#MYARR[@]}

Associative Array

Indirect array access

Local array with indirect contents:

eval declare -r -a local_array=\( \${${indirect}[@]} \)

Function argument passing

declare -A weapons=(
    ['Straight Sword']=75
    ['Tainted Dagger']=54
    ['Imperial Sword']=90
    ['Edged Shuriken']=25
)

function print_array {
    eval "declare -A arg_array="${1#*=}
    for i in "${!arg_array[@]}"; do
        printf "%s\n" "$i ==> ${arg_array[$i]}"
    done
}
print_array "$(declare -p weapons)"

Remove key

unset MYMAP["${key}"]

Length

declare -A MYMAP=( [a]=1 [b]=2 )
echo ${#MYMAP[@]}  # 2

Keys, sorted

# lines
sort < <(for i in "${!MYMAP[@]}"; do echo "$i"; done)

# to array / on single line
declare -a sorted=( $(sort < <(for i in "${!MYMAP[@]}"; do echo "$i"; done)) )
echo "${sorted[@]}"

Print keys

echo "${!MYMAP[@]}"

Iterate keys

for key in "${!MYMAP[@]}"; do
  echo "${key}"
done

Does key exist?

if [[ "${MYMAP["${key}"]+_}" ]]; then
    echo "Found"
else
    echo "Not found"
fi

Case statements

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_03.html

case $TYPE in
    either|or)
        stuff...
        ;;
    *)
        default_stuff...
        ;;
esac

Redirect stderr only

no_mysql_insecure_password_warning()
{
    sed -e '/Using a password on the command line interface can be insecure/d'
}

mysql "$@" 2> >(no_mysql_insecure_password_warning)

Redirect stderr

To stdout:

cmd 2>&1

Use the following syntax:

cmd &>file

OR

cmd > file-name 2>&1

Another useful example:

find /usr/home -name .profile 2>&1 | more

Read

Tab delimited input

IFS=$'\t' read -r user hash length filename \
    < <(mysql-query.sh --query "SELECT \`user\`, \`hash\`, \`length\`, \`filename\` FROM \`my_db\`.\`file_stuff\` LIMIT 1")
echo "User: $user  Hash: $hash  Length: $length  Filename: $filename"

Interactive Environment

History

Skip adding lines to history that begin with spaces

HISTCONTROL=ignorespace

Don’t add lines that exist already.

HISTCONTROL=ignoredups

Remove lines in past that match new entry.

HISTCONTROL=erasedups

Multiple entries are separated by a colon.

Patterns

Arrays

Get elements in array

Use “#” with “[@]”.

WARNING: Using “#” without the “[@]” will cause you to only output the number of characters in the first element of the array.

echo ${#an_array[@]}

Pipe array to command

sort < <(for i in "${range_ip_and_ports[@]}"; do echo "$i"; done)

Command Line Arguments

Check if script is sourced

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  echo "Is run directly"
else
  echo "Is sourced"
fi

# Or
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"

Program takes commands which map to function names

# Commands should be in this array AND have a command_CMDNAME function.
declare -a commands=(help)

log_stderr() { cat <<< "$@" 1>&2; }

is_a_function() { [[  $(type -t "$1") == "function" ]]; }

command_help()
{
    cat <<-EOF
Some text to display.
EOF
}

main_switcn()
{
    declare -r command="$1"
    declare -r command_function="command_${command}"
    shift

    if ! elem "${command}" "${commands[@]}"; then
        log_stderr "\"${command}\" is not a command."
        log_stderr "Commands: $(print_join_array ", " "${commands[@]}")"
        return 1
    elif ! is_a_function "${command_function}"; then
        log_stderr "Missing function \"${command_function}\" for command \"${command}\"."
        return 1
    else
        "${command_function}" "$@"
    fi
}
if [[ $# -eq 0 ]]; then
    command_help
else
    main_switcn "$@"
fi

Parse arguments

#!/bin/bash

while [[ $# -gt 0 ]]; do
    key="$1"

    case $key in
        -e|--extension)
            EXTENSION="$2"
            shift # past argument
            ;;
        -s|--searchpath)
            SEARCHPATH="$2"
            shift # past argument
            ;;
        -l|--lib)
            LIBPATH="$2"
            shift # past argument
            ;;
        --default)
            DEFAULT=YES
            ;;
        *)
            # unknown option
            ;;
    esac
    shift # past argument or value
done

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

Parse arguments (main)

#!/bin/bash

declare -r this_script_filename="$(basename "${BASH_SOURCE[0]}")"

stderr() { cat <<< "$@" 1>&2; }

print_help()
{
    cat <<EOF
Usage: ${this_script_filename} ...

ARGS:
  -h, --help     This help

...DESCRIPTION HERE...
EOF
}

main()
{
    local key
    while [[ $# -gt 0 ]]; do
        key="$1"
        case $key in
            -h|--help)
                print_help
                return 0
                ;;
            -s|--searchpath)
                SEARCHPATH="${2:?}"
                shift # past argument
                ;;
            --default)
                DEFAULT=YES
                ;;
            *)
                # unknown option
                printf 'Unknown option "%s"\n\n' "${key}" 1>&2
                print_help 1>&2
                return 1
                ;;
        esac
        shift # past argument or value
    done

    # do stuff...
}

[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"

Command line argument handling

declare -r this_script_filename="$(basename "${BASH_SOURCE[0]}")"
declare -r -a argv=("$@")

print_help()
{
    cat <<EOF
Usage: ${this_script_filename} ...

ARGS:
  --help   -h   This help

...DESCRIPTION HERE...
EOF
}

is_flag_set()
{
    for flag in "$@"; do
        if contains_element "$flag" "${argv[@]}"; then
            return 0
        fi
    done
    return 1
}

contains_element()
{
    local e
    for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
    return 1
}

if is_flag_set "--help" "-h"; then
    print_help
    exit
fi

main

Simple –help

if [ "${1:-}" = "--help" ] \
       || [ "${1:-}" = "-h" ]
then
    cat <<EOF
Usage: $0 ...

ARGS:
  --help   -h   This help

Description
EOF
    exit 0
fi

Exit codes

#Descriptionsysexit.h
0SuccessEX_OK
1Catchall/generic error
2Shell builtin misuse
64Command line usage errorEX_USAGE
65Data format errorEX_DATAERR
66cannot open inputEX_NOINPUT
67addressee unknownEX_NOUSER
68host name unknownEX_NOHOST
69service unavailableEX_UNAVAILABLE
70internal software errorEX_SOFTWARE
71system error (eg can’t fork)EX_OSERR
72critical OS file missingEX_OSFILE
73can’t create user output fileEX_CANTCREAT
74input/output errorEX_IOERR
75temp failure(can retry)EX_TEMPFAIL
76remote error in protocolEX_PROTOCOL
77permission deniedEX_NOPERM
78configuration errorEX_CONFIG
126Command invoked cannot execute
127Command not found
128Invalid argument to exit
128+nFatal error signal n
255Exit status out of range(Only 0-255 valid)

Expect-like pipes

Use empty:

http://empty.sourceforge.net/

Files

Don’t inherit parent file descriptors

no_inherit_fds()
{
    ( # Subshell:
        # Close all but stdio
        SELF=$BASHPID
        FDS=$(find /proc/$SELF/fd -type l -printf '%f\n')
        # The following will even try to close the fd for the find sub
        # shell although it is already closed. (0: stdin, 1: stdout, 2:
        # stderr, 3: find)
        for n in $FDS ; do
            if ((n > 2)) ; then
                eval "exec $n>&-"
            fi
        done

        "$@"
    )
}

Read file into variable

local contents
if ! [[ -e $file_path ]] || ! contents=$(<"${file_path}"); then
    return 1
fi

Read file into array

declare -a lines
while IFS='' read -r line || [[ -n "$line" ]]; do
    lines+=("${line}")
done < "${file_path}"

Read file line by line

while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "${file_path}"

Process lines from variable

while IFS= read -r line; do
    echo "${line}"
done <<< "${var}"

Get current directory

declare -r this_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Convert relative link to absolute

realpath "${dir}"

Get text and line count

Style 1

declare -r ps_defunct="$(ps ax | grep '<[d]efunct>')"
declare -r -i defunct_count="$([[ -n $ps_defunct ]] && wc -l <<< "${ps_defunct}")"

Style 2

local lines_raw
# Use read to prevent trailing newline trimming
IFS= read -rd '' lines_raw < <(
  dmesg | tail -n 1000 |
    egrep -i 'hda|hdb|hdc|sda|sdb|sdc|sdd|sde|sdf|sdg|ata|ide')
declare -r -i count=$(echo -n "$lines_raw" | wc -l)

Logging

Rolling logfile by day

declare -r -i logfile_count=3
declare -r -i today_logfile_num=$(( $(date +%j) % logfile_count ))
declare -r logfile="/lrtemp/logs/set_deleted_date_for_all_trashcan_children.php.${today_logfile_num}.log"

do_something_daily > "${logfile}"

Syslog logging

logger_id="$(basename "$0")"
logger_facility=user
log_emerg()   { logger -s -t "${logger_id}" -p "${logger_facility}.emerg"   <<< "$@"; }
log_alert()   { logger -s -t "${logger_id}" -p "${logger_facility}.alert"   <<< "$@"; }
log_crit()    { logger -s -t "${logger_id}" -p "${logger_facility}.crit"    <<< "$@"; }
log_err()     { logger -s -t "${logger_id}" -p "${logger_facility}.err"     <<< "$@"; }
log_warning() { logger -s -t "${logger_id}" -p "${logger_facility}.warning" <<< "$@"; }
log_notice()  { logger -s -t "${logger_id}" -p "${logger_facility}.notice"  <<< "$@"; }
log_info()    { logger -s -t "${logger_id}" -p "${logger_facility}.info"    <<< "$@"; }
log_debug()   { logger -s -t "${logger_id}" -p "${logger_facility}.debug"   <<< "$@"; }
log()         { log_notice "$@"; }
stderr()      { cat <<< "$@" 1>&2; }
eprintf()     { printf "$@" 1>&2; }

Enable debug info

# Echo script as it is processed
set -v

# Output commands run, with variables replaced with actual contents
set -x

Log function output to system log

func()
{
    echo "to syslog"
} 1> >(logger -s -t "$(basename "$0")") 2>&1 # redirect all output to system log

Log all output to system log

At top of file:

# redirect all output to system log
exec 1> >(logger -s -t "$(basename "$0")") 2>&1

Log to file

declare -r log_file_path=/tmp/some_file.log

log() { cat <<< "$@" >> "${log_file_path}"; }

Log to with timestamp

declare -r log_file_path=/tmp/some_file.log

log() { echo "$(date '+%Y%m%d-%H%M%S') $*" | tee -a "${log_file_path}"; } 2>&1

Log to file with name

declare -r this_script_filename="$(basename "${BASH_SOURCE[0]}")"
declare -r LOGFILE="/var/log/linkright.log"
export LOGFILE
log()
{
    declare -a output=("${this_script_filename}:" "$@")
    cat <<< "${output[@]}" >> "${LOGFILE}"
}

Log to stderr

log() { cat <<< "$@" 1>&2; }

stderr() { cat <<< "$@" 1>&2; }

Loops

Process words in line/split by words

Convert line to array.

for loop

for (( x=0; x<2; ++x )); do
    echo $x
done

Process lines from variable

If contents from read, may have extra empty line at end.

while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from contents: $line"
done <<< "${contents}"

Process lines from file

while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "${file_path}"

Process lines from command output

while IFS= read -r line; do
    echo "${line}"
done < <(some_comand with_args)

Safe find loop

while IFS= read -r -d $'\0' file; do
    echo "${file}"
done < <(find /lib64/ -maxdepth 1 -type f -print0)

# Into an array
declare -a files
while IFS= read -r -d $'\0' file; do
    files+=("${file}")
done < <(find /lib64/ -maxdepth 1 -type f -print0)

Misc

Get regex from line

Bash uses: https://www.gnu.org/software/libc/manual/html_node/Regular-Expressions.html

# Example:
#   $ regex_extract "\b((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\b" "http://13.24.35.255:13"
#   13.24.35.255
regex_extract()
{
    declare -r re="$1"
    if [[ $2 =~ $re ]]; then
        echo "${BASH_REMATCH[0]}"
        return 0
    fi
    return 1
}

# Public: Extract the nth group
#
# Finds matching line and outputs the given group.
#
# $1 - Group number to match.  0=all
# $2 - Regular expression
# $3-* - Search string
#
# Example
#
#   local response seconds
#   if ! response=$(regex_extract_n 1 "^\[([^]]+)\]" "$*"); then
#       # Matches digits out of "[123]...."
#       return 1
#   fi
#
# On success returns 0 and outputs matching section.
regex_extract_n()
{
    declare -r re="$2"
    if [[ $3 =~ $re ]]; then
        echo "${BASH_REMATCH[$1]}"
        return 0
    fi
    return 1
}

Inline compilcated awk

print_cpu_count()
{
    read -r -d '' awk_script << 'EOF'
  BEGIN {
      count = 0
  }

  {
      if(NF == 3 && $1 == "processor")
      {
          count++
      }
  }

  END {
      print count
  }

EOF

    /bin/awk "${awk_script}" /proc/cpuinfo
}

print_uptime_seconds() { awk '{print $1}' /proc/uptime | sed 's/\..*//'; }
dmesg_from_last_x_seconds() {
    declare -r -i second_count="$1"
    declare -r -i current_second="$(print_uptime_seconds)"
    declare -r -i start_second=$(( current_second - second_count ))

    read -r -d '' awk_script << 'EOF'
{
    if (started) {
        print
    } else if (match($0, /^\[([0-9]+)\./, matches) != 0 && matches[1] > start_second) {
        started = 1
        print
    }
}
EOF
    dmesg | /bin/awk -v start_second="${start_second}" "${awk_script}"
}

Keep ssh session alive by writing to pts

for ((i=0;i<60;++i)); do printf '.' > /dev/pts/1; date; sleep 60; done
while true; do printf '.' | tee /dev/pts/1; sleep $((60*4)); done

Output variable escaped to be used as argument

printf '%q\n' 'This is a "test"'

Permit single instance of a script to run (lockfile)

declare -r lockfile_path="/tmp/$(basename "${BASH_SOURCE[0]}").lock"
mutually_exclusive_main()
{
    # This is auto-populated by bash at bottom of function
    local instance_fd
    (
        # Don't wait for lock on lockfile (fd automatically allocated)
        if flock --nonblock --exclusive ${instance_fd}; then
            main "$@"
        else
            echo "Unable to run.  Already running." 1>&2
            return 1
        fi
    ) {instance_fd}> "${lockfile_path}"
}
mutually_exclusive_main "$@"

mutually_exclusive_blocking_main()
{
    # This is auto-populated by bash at bottom of function
    local instance_fd
    (
        # Wait for lock on lockfile (fd automatically allocated)
        flock --exclusive ${instance_fd}
        main
    ) {instance_fd}> "${lockfile_path}"
}

Taper a command to run over a period of time

for (( divisions=10, seconds_inbetween=60, cur=0; cur<divisions ; ++cur )); do
    if [ $(( $(hostname | sed 's/[^0-9]//g') % divisions )) -eq $cur ]; then
        echo "Performing action"
        break
    fi
    sleep "${seconds_inbetween}"
done

Traps (exit or on signal)

“man trap” for documentation.

trap [action condition…]

  • Signal names should drop the “SIG” prefix.
  • Null action ” means ignore the conditions.
  • Action ‘-’ means reset each condition to default.

Signals:

SignalNumberActionDescriptionNon-catchable
EXIT0TermScript completes
HUP1TermTerminal disconnected
INT2CoreInterrupt from keyboard
QUIT3CoreQuit from keyboard
ILL4CoreIllegal instruction
ABRT6CoreSignal from abort()
FPE8CoreFloating point exception
KILL9TermKill signalX
SEGV11CoreInvalid memory referenced
PIPE13TermBroken pipe: write to pipe with no readers
ALRM14TermTimer signal from alarm()
TERM15TermTermination signal
USR130,10,16TermUser defined 1
USR231,12,17TermUser defined 2
CHLD20,17,18IgnChild stopped or terminated
CONT19,18,25ContContinue process
STOP17,19,23StopStop processX
TSTP18,20,24StopStop typed at terminal
ActionsDefault action
TermTerminate the process
IgnIgnore the signal
CoreTerminate the process and dump core
StopStop the process
ContContinue process if stopped

Use bash in cronjob

At top of file add the following:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

Random

Only perform action randomly

if (((RANDOM % 20) == 0)); then
    echo Random action
fi

Get randomized range or arguments

0 to 30 by 3s, randomized

shuf -e $(seq 0 3 30)

0 to 5, randomized

shuf -i 0-5

Return array

array_returning_function()
{
    printf '%q ' \
           1 \
           "a b c" \
           2 \
           '15 "22" 19' \
           END
}

main()
{
    local arr
    if ! arr="$(array_returning_function)"; then
        echo FAILED
        return 1
    fi
    eval arr=\($arr\)

    printf '(%s)\n' "${arr[@]}"
}

main

Return associative array

printf -v newline '\n'

assoc_array_returning_function()
{
    declare -A ret
    ret[one]='a b c'
    ret[two]='15 "22" 19'
    ret[three]="${newline}"
    ret[four]='END'
    ret[f i v e]='s i x'

    # associative array return
    for key in "${!ret[@]}"; do
        printf '[%q]=%q ' "${key}" "${ret["${key}"]}"
    done
}

main()
{
    local result
    if ! result="$(assoc_array_returning_function)"; then
        return 1
    fi
    eval declare -A result=\(${result}\)

    for key in "${!result[@]}"; do
        printf '(:KEY %s :VALUE %s)\n' "${key}" "${result["${key}"]}"
    done
    # (:KEY four :VALUE END)
    # (:KEY one :VALUE a b c)
    # (:KEY f i v e :VALUE s i x)
    # (:KEY two :VALUE 15 "22" 19)
    # (:KEY three :VALUE
    # )
}

main

Store program data in CSV like table

From variable

IFS='' read -r -d '' data <<'EOF'
,abc,123
def,,456
ghi,789,
EOF

map_data()
{
    declare -r split_regex=$1
    declare -r data=$2
    shift 2

    while read -r line; do
        preg_split "${split_regex}" "${line}" "$@"
    done < <(echo -n "${data}")
}

trim()
{
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    echo -n "$var"
}

preg_split()
{
    declare -r regex=$1
    declare -r string=$2
    shift 2
    declare -a func_args=("$@")

    declare -r re="^(.*)${regex}(.*)$"
    local scan=$string
    declare -a args

    while [[ $scan =~ $re ]]; do
        if [[ $scan = "${BASH_REMATCH[1]}" ]]; then
            return 1
        fi

        args+=("${BASH_REMATCH[*]: -1:1}")
        scan="${BASH_REMATCH[1]}"
    done

    # Stop if nothing split
    if [[ ${#args[*]} -eq 0 ]]; then
        return 1
    fi

    func_args+=("$(trim "${scan}")")
    declare -i i
    for ((i = ${#args[*]} - 1; i >= 0; --i)); do
        func_args+=("$(trim "${args[$i]}")")
    done

    "${func_args[@]}"
}

map_data "," "${data}" printf '"%s" "%s" "%s"\n'

From stdin

map_stdin_by_separator()
{
    declare -r split_regex=$1
    shift 1

    while read -r line; do
        preg_split "${split_regex}" "${line}" "$@"
    done
}

trim()
{
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    echo -n "$var"
}

preg_split()
{
    declare -r regex=$1
    declare -r string=$2
    shift 2
    declare -a func_args=("$@")

    declare -r re="^(.*)${regex}(.*)$"
    local scan=$string
    declare -a args

    while [[ $scan =~ $re ]]; do
        if [[ $scan = "${BASH_REMATCH[1]}" ]]; then
            return 1
        fi

        args+=("${BASH_REMATCH[*]: -1:1}")
        scan="${BASH_REMATCH[1]}"
    done

    # Stop if nothing split
    if [[ ${#args[*]} -eq 0 ]]; then
        return 1
    fi

    func_args+=("$(trim "${scan}")")
    declare -i i
    for ((i = ${#args[*]} - 1; i >= 0; --i)); do
        func_args+=("$(trim "${args[$i]}")")
    done

    "${func_args[@]}"
}

map_stdin_by_separator "," printf '"%s" "%s" "%s"\n' <<'EOF'
,abc,123
def,,456
ghi,789,
EOF

Test if terminal

declare -r -i is_terminal=$([[ -t 0 ]] && echo 1)

Time

Parse date

now_unix_timestamp()
{
    date '+%s'
}

to_unix_timestamp()
{
    date --date="$*" "+%s"
}

timestamp_to_human()
{
    declare -r -i unix_timestamp=$1
    date --date="@${unix_timestamp}" --rfc-3339=seconds
}

Parse date into parts

to_year_month_day_hour_minute_second()
{
    date --date="$*" "+%Y %m %d %H %M %S"
}

date_array_compare()
{
    declare -a date_a
    declare -a date_b

    if ! date_a=( $(to_year_month_day_hour_minute_second "$1") ); then
        return 1
    elif ! date_b=( $(to_year_month_day_hour_minute_second "$2") ); then
        return 2
    fi

    for pos in $(seq 1 6); do
        if [ "${date_a[$pos]}" -lt "${date_b[$pos]}" ]; then
            echo "-1"
            return 0
        elif [ "${date_a[$pos]}" -gt "${date_b[$pos]}" ]; then
            echo "1"
            return 0
        fi
    done

    echo "0"
    return 0
}

is_date_in_range()
{
    local result
    if ! result=$(date_array_compare "$2" "$1"); then
        return 2
    fi

    if [ "$result" = "1" ]; then
        return 1
    fi

    if ! result=$(date_array_compare "$3" "$1"); then
        return 2
    fi

    if [ "$result" = "-1" ]; then
        return 1
    fi

    return 0
}

User Interaction

Read data from stdin at request

main()
{
    declare user_data=${1:-}
    if [[ -z $user_data ]]; then
        user_data=$(</dev/stdin)
        if [[ -z $user_data ]]; then
            return 1
        fi
    fi

    echo "Got: ${user_data}"
}
main "$@"

Ask

# Public: Ask user for info with optional regex check
#
# --prompt           -p  Prompt to use.
# --verify-by-regex      Regex to verify input by.
#
# Example
#
#   ask --prompt "The date?" \
#       --verify-by-regex '^[12][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])$'
#   > The date? 1920-12-32
#   > Unvalid input.
#
# Returns user response on success.  Otherwise non 0 return code.
ask()
{
    local input
    declare prompt=""
    declare validation_regex=""
    declare -i status

    while [[ $# -gt 0 ]]; do
        case $1 in
            -p|--prompt)
                prompt="$2"; shift
                ;;
            --verify-by-regex)
                validation_regex="$2"; shift
                ;;
            -*)
                # unknown option
                printf 'Unknown argument: %s\n' "$1" 1>&2
                return 1
                ;;
            *)
                if [[ -n $prompt ]]; then
                    printf 'Prompt already set to "%s".  Can not reset to: %s\n' \
                           "$prompt" "$1" 1>&2
                    return 1
                fi
                prompt="$1"
                ;;
        esac
        shift # past argument or value
    done

    while true; do
        if [[ -n $prompt ]]; then
            printf '%s ' "$prompt" 1>&2
        fi
        read -r input
        status=$?

        if ((status != 0)); then
            printf 'Read user input failure. (%d)\n' "${status}" 1>&2
            return 1
        fi

        if [[ -z $validation_regex ]]; then
            break
        fi

        if [[ $input =~ $validation_regex ]]; then
            break
        fi

        printf 'Unvalid input.\n' 1>&2
    done

    printf '%s' "${input}"
}

Ask confirmation

read -p "Are you sure? " -n 1 -r
echo    # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]; then
    # do dangerous stuff
fi

Useful functions

Arrays

member - Check if element is in list

member() { local e; for e in "${@:2}"; do [[ $e = "$1" ]] && return 0; done; return 1; }

Numbers

integer?

integer? () { [[ $1 =~ ^[0-9]+$ ]]; }

is natural number?

is_natural_number () { [[ $1 =~ ^[1-9][0-9]*$ ]]; }

min

min () { for i in "$@"; do ((i < t)) && t=$i; done; printf '%d' "$t"; }

max

max () { for i in "$@"; do ((i > t)) && t=$i; done; printf '%d' "$t"; }

Strip starting zeros

strip-leading-0 () { sed 's/^0\+//' <<< "$*"; }
strip-leading-0-pipe() { sed 's/^0\+//'; }

Random range

rand_range_safe()
{
    declare -i rmin=${1:?}
    declare -i rmax=${2:?}
    if ((rmin > rmax)); then
        declare -i rtmp=${rmax}
        rmax=${rmin}
        rmin=${rtmp}
    fi

    declare -i range=$((rmax + 1 - rmin))
    declare -r -i max_random=32767
    declare -i return_code=0
    if ((range > max_random)); then
        range=${max_random}
        return_code=1
    fi

    declare -r -i max_allowed=$(((max_random % range) == 0 ? max_random : max_random - (max_random % range) - 1))

    declare -i random_value=$RANDOM
    while ((random_value > max_allowed)); do
        random_value=$RANDOM
    done

    printf '%d' $((rmin + (random_value % range)))
    return ${return_code}
}

sleep $(rand_range 10 20)

Float math

multiply_f()
{
    echo "$1 * $2" | bc -l
}

subtract_f()
{
    echo "$1 - $2" | bc -l
}

Cast float to int

cast_to_int()
{
    printf '%.0f\n' "$1"
}

String Manipulation

string replace

# Public: Replace one instance of a pattern in a string.
#
# $1 - Pattern to find
# $2 - Replacement for pattern
# $3 - String to search
#
# Example
#
#   replace_one "a" "b" "aaa"
#   # baa
#
# Writes search string, with pattern replaced if found.
replace_one()
{
    declare -r pattern="${1:?}"
    declare -r replacement="${2:?}"
    declare -r string="${3:?}"

    printf "%s" "${string/$pattern/$replacement}"
}

# Public: Replace all instances of a pattern in a string.
#
# $1 - Pattern to find
# $2 - Replacement for pattern
# $3 - String to search
#
# Example
#
#   replace_all "a" "b" "aaa"
#   # bbb
#
# Writes search string, with pattern replaced if found.
replace_all()
{
    declare -r pattern="${1:?}"
    declare -r replacement="${2:?}"
    declare -r string="${3:?}"

    printf "%s" "${string//$pattern/$replacement}"
}

to_upper

to_upper()
{
    if ((! $#)); then
        tr '[:lower:]' '[:upper:]'
    else
        tr '[:lower:]' '[:upper:]' <<< "$*"
    fi
}

Eliminate extra newlines

reduce_whitespace()
{
    sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/[[:space:]][[:space:]]*/ /g'
}

Trim whitespace

# Reads from stdin
trim()
{
    sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}

# Pure posix, takes args
trim()
{
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    echo -n "$var"
}

Line Manipulation

indent

indent() { while IFS= read -r line; do printf '%*s%s\n' "${1:-2}" '' "${line}"; done; }

Remove lines

unlines()
{
    declare -r lines="$*"
    echo "${lines//
/ }"
}

unlines_with()
{
    declare -r sep="$1"; shift
    declare -r lines="$*"
    echo "${lines//
/${sep}}"
}

Files

Get file mtime

get_file_mtime()
{
    /usr/bin/stat --printf="%Y" "$1"
}

file_modified_seconds_ago()
{
    declare -r path=$1
    local -i mtime now
    if ! mtime=$(/usr/bin/stat --printf="%Y" "${path}"); then
        return 1
    fi
    if ! now=$(date '+%s'); then
        return 1
    fi

    echo $((now - mtime))
}

Get file size

get_file_size()
{
    /usr/bin/stat --printf="%s" "$1"
}

basename

basename.() { basename "$1" | cut -d. -f1; }

file_x_modified_within_y_minutes

## @fn file_x_modified_within_y_minutes
## @brief Check if a file exists with an mtime within range
##
## @param $1 File path
## @param $2 Minute range
##
## @return 0 if file exists and has mtime in past "range" minutes.
##
## @usage
##   file_x_modified_within_y_minutes \
##       "${touch_file_name}" \
##       "${no_send_more_often_than_minutes}"
##   if [ $? -ne 0 ]; then
##       # ... do stuff
##       tmp_touch_file "${touch_file_name}"
##   fi
file_x_modified_within_y_minutes()
{
    declare -r file_path="$1"
    declare -r -i minutes="$2"

    declare -i count

    count="$(find "$(dirname "${file_path}")/" \
                    -maxdepth 1 \
                    -mmin "-${minutes}" \
                    -name "$(basename "${file_path}")" \
                | wc -l)"

    if [ "${count}" -eq 0 ]; then
        return 1
    else
        return 0
    fi
}

mktemp wrappers

with_temp_file

with_temp_file()
{
    declare to_call="${1:?}"
    shift
    local path
    if ! path="$(mktemp)"; then
        return 1
    fi

    trap "rm -f ${tmp_files[*]}" EXIT
    "${to_call}" "$@" "${path}"
    declare -r -i result=$?

    /bin/rm "${path}"
    trap - EXIT

    return "${result}"
}

with_temp_files

with_temp_files()
{
    declare -r -i count=${1:?}; shift
    declare -a tmp_files
    local path

    for ((i=0; i<count; ++i)); do
        if ! path="$(mktemp)"; then
            log "Failed to create temp file."
            for path in "${tmp_files[@]}"; do
                /bin/rm "${path:?}"
            done
            return 1
        fi
        tmp_files+=("${path}")
    done

    trap "rm -f ${tmp_files[*]}" EXIT

    "$@" "${tmp_files[@]}"
    declare -r -i result=$?

    for path in "${tmp_files[@]}"; do
        /bin/rm "${path:?}" &>/dev/null
    done

    trap - EXIT

    return "${result}"
}

Usage with vararg function

varargs_example_inner()
{
    declare -r stderr1="${@: -1:1}"
    declare -r stderr2="${@: -2:1}"
    declare -r stderr3="${@: -3:1}"

    # Clear temp files from arguments
    set -- "${@:1: $(($#-3))}"
    printf "%s " "$@"
}

varargs_example()
{
    with_temp_files 3 varargs_example_inner "$@"
}

varargs_example 1 2 3
# Prints: 1 2 3

Time

Seconds to human

Seconds to human

seconds_to_human() {
    declare -r -i T=$1
    declare -r -i D=$((T/60/60/24))
    declare -r -i H=$((T/60/60%24))
    declare -r -i M=$((T/60%60))
    declare -r -i S=$((T%60))
    ((D>0)) && printf '%d days ' $D
    ((H>0)) && printf '%d hours ' $H
    ((M>0)) && printf '%d minutes ' $M
    ((M>0||H>0||D>0)) && printf 'and '
    printf '%d seconds\n' $S
}

Seconds to 1 char summary

seconds_to_summary() {
    declare -r -i T=$1
    declare -r -i D=$((T/60/60/24))
    ((D>0)) && { printf '%dd' $D; return; }
    declare -r -i H=$((T/60/60))
    ((H>0)) && { printf '%dh' $H; return; }
    declare -r -i M=$((T/60))
    ((M>0)) && { printf '%dm' $M; return; }
    printf '%ds' "$T"
}

Seconds to 1 char long summary

seconds_to_summary() {
    declare p=''
    declare -i n=2
    declare -r -i T=$1
    declare -r -i D=$((T/60/60/24))
    ((D>0)) && { printf '%s%dd' "$p" $D; p=' '; n+=-1; }
    declare -r -i H=$((T/60/60%24))
    ((H>0)) && { printf '%s%dh' "$p" $H; p=' '; n+=-1; }
    declare -r -i M=$((T/60%60))
    ((M>0 && n>0)) && { printf '%s%dm' "$p" $M; p=' '; n+=-1; }
    declare -r -i S=$((T%60))
    ((S>0 && n>0)) && { printf '%s%ds' "$p"  $S; }
}

Reset mtime to nothing

touch -d@0 FILEPATH

Seconds process running

seconds_running() {
    declare -i now start_time
    if ! now=$(date +%s) || ! start_time=$(stat --format=%Y /proc/${1:?}); then
        return 1
    fi
    echo $((now - start_time))
}

for pid in $(pgrep "^hhvm$"); do
    seconds_running "${pid}"
    if [[ $(seconds_running "${pid}") -lt 120 ]]; then
        echo Short running process: $pid
    fi
done

Nice timestamps

print_timestamp_min()
{
    date '+%Y%m%d-%H%M'
}

print_timestamp_sec()
{
    date '+%Y%m%d-%H%M%S'
}

month_str_to_num

month_str_to_num()
{
    declare -i idx
    idx=$(strindex JanFebMarAprMayJunJulAugSepOctNovDec "$1")
    if [ "$idx" -ne -1 ]; then
        echo $((idx / 3 + 1))
    fi
}
# > month_str_to_num Oct
# 10

Get seconds with milliseconds

print_seconds_and_milliseconds()
{
    /bin/date "+%s.%3N"
}

Paths

Relative path

realpath --relative-to="$file1" "$file2"
# For example:
realpath --relative-to=/usr/bin/nmap /tmp/testing
# ../../../tmp/testing

Parsing

Number from inside string

extract_number_group_n()
{
    declare -r str=$1
    declare -r -i group=${2:-0}

    local ch
    declare -i i digitp inside_number=0 current_group=0
    for ((i = 0; i < ${#str}; ++i)); do
        ch=${str:$i:1}
        digitp=$([[ $ch != [0-9] ]]; echo $?)

        if ((inside_number)); then
            if ((group == current_group)); then
                if ((!digitp)); then
                    printf '\n'
                    return 0
                fi
                printf '%s' "$ch"
            else
                inside_number=0
                current_group+=1
            fi
        elif ((digitp)); then
            inside_number=1
            if ((group == current_group)); then
                printf '%s' "$ch"
            fi
        fi
    done
    if ((inside_number && group == current_group)); then
        printf '\n'
        return 0
    fi
    return 1
}
# declare -r -i coefficient=$(extract_number_group_n "$HOSTNAME" 0 || echo 0)

Processes

Track memory usage

seconds_to_summary() {
    declare -r -i T=$1
    declare -r -i D=$((T/60/60/24))
    ((D>0)) && { printf '%dd' $D; return; }
    declare -r -i H=$((T/60/60))
    ((H>0)) && { printf '%dh' $H; return; }
    declare -r -i M=$((T/60))
    ((M>0)) && { printf '%dm' $M; return; }
    printf '%ds' "$T"
}

track_memory_usage () {
    declare -r -i pid=${1:?}
    local total_line start_total_kb total_kb output last_output start_time now
    declare -i difference

    start_time=$(date '+%s')

    total_line=$(pmap -x "${pid}" | grep ^total)
    if ! [[ $total_line ]]; then
        echo "No info for pid $pid" 1>&2;
        return 1
    fi

    set -- ${total_line}
    start_total_kb=$3  # rss_kb=$3 dirty_kb=$4

    echo "${start_total_kb} KB"
    #for ((i = 0; i < 10; ++i)); do
    while true; do
        total_line=$(pmap -x "${pid}" | grep ^total)
        if ! [[ $total_line ]]; then
            echo "Process terminated"
            return 1
        fi
        total_kb=$3

        output="$(date +%H:%M) Start: ${start_total_kb} KB  Current: ${total_kb} KB"
        difference=$((total_kb - start_total_kb))
        if ((difference != 0)); then
            now=$(date '+%s')
            output="$output  Difference: ${difference} KB over $(seconds_to_summary $((now - start_time)))"
        fi

        if [[ $output != "${last_output}" ]]; then
            clear
            echo "$output"
            last_output=$output
        fi
        sleep 1
    done
}

Filtering

php.log

filter_php_log_recent_seconds()
{
    declare -r -i seconds="${1:?}"; shift
    declare -r -a files=("$@")

    read -r -d '' awk_script << 'EOF'
BEGIN {
    m["Jan"]=1; m["Feb"]=2; m["Mar"]=3; m["Apr"]=4; m["May"]=5; m["Jun"]=6
    m["Jul"]=7; m["Aug"]=8; m["Sep"]=9; m["Oct"]=10; m["Nov"]=11; m["Dec"]=12
    start = systime() - seconds
}
{if (do_print == 1) {print; next}}
/^\[/ {
    if (match($0, /^\[[A-Z][a-z][a-z] ([A-Z][a-z][a-z]) ([1-9][0-9]*) ([01][0-9]):([01][0-9]):([01][0-9]) (20[0-9][0-9])\]/, d) == 1) {
        ts = mktime(sprintf("%d %02d %02d %02d %02d %02d", d[6], m[d[1]], d[2], d[3], d[4], d[5]))
        if (ts > 0 && start <= ts) {
            print
            do_print = 1
        }
    }
}
EOF

    /bin/awk -v seconds="${seconds}" "${awk_script}" "${files[@]}"
}

/var/log/messages

filter_var_messages_recent_seconds()
{
    declare -r -i seconds="${1:?}"; shift
    declare -r -a files=("$@")

    read -r -d '' awk_script << 'EOF'
BEGIN {
    m["Jan"]=1; m["Feb"]=2; m["Mar"]=3; m["Apr"]=4; m["May"]=5; m["Jun"]=6
    m["Jul"]=7; m["Aug"]=8; m["Sep"]=9; m["Oct"]=10; m["Nov"]=11; m["Dec"]=12
    year=strftime("%Y")
    start = systime() - seconds
}
{
    if (do_print == 1) {print; next}
    if (match($0, /^([A-Z][a-z][a-z]) ([1-9][0-9]*) ([01][0-9]):([01][0-9]):([01][0-9]) /, d) == 1) {
        ts = mktime(sprintf("%d %02d %02d %02d %02d %02d", year, m[d[1]], d[2], d[3], d[4], d[5]))
        if (ts > 0 && start <= ts) {
            print
            do_print = 1
        }
    }
}
EOF

    /bin/awk -v seconds="${seconds}" "${awk_script}" "${files[@]}"
}

Function manipulation

Anonymous functions

gensym()
{
    declare -r gensym_file=/tmp/.bash_gensym
    declare -r gensym_lock_file=${gensym_file}.lock

    local instance_fd
    (
        # Wait for lock on lockfile (fd automatically allocated)
        flock --exclusive ${instance_fd}
        declare -i gennum=$(<"${gensym_file}")
        gennum+=1
        echo "${gennum}" > "${gensym_file}"
        echo "f${BASHPID}_${gennum}"
    ) {instance_fd}> "${gensym_lock_file}" 2>/dev/null
}

lambda()
{
    declare s="$1() { "; shift
    for arg in "$@"; do printf -v s '%s\n%b' "${s}" "${arg}"; done
    eval "$s
}"
}

map_lambda() {
    local f="$(gensym)"
    lambda "$f" "$1"
    shift
    for i in "$@"; do
        "$f" "$i"
    done
    unset -f "$f"
}

reduce_lambda() {
    local f="$(gensym)"
    lambda "$f" "$1"
    local acc="$2"
    shift 2
    for i in "$@"; do
        acc=$("$f" "${acc}" "$i")
    done
    unset -f "$f"
    printf '%s' "${acc}"
}

main()
{
    local f="$(gensym)"

    # Escaping only
    lambda "$f" 'echo "1:$1"; echo "2:$2"'
    "$f" Test1 Test2

    lambda "$f" 'for val in "$@"; do echo "${val}"; done;'
    "$f" "Test_A" "Test B"

    lambda "$f" \
           'for val in "$@"; do' \
           '  echo "${val}"' \
           'done'
    "$f" "Test_A" "Test B"

    map_lambda 'echo "Lambda sees $1"' \
               $(seq 1 3)

    reduce_lambda 'echo $(($1 + $2))' 0 \
                  $(seq 1 100)
}

Anonymous function passing and usage

# An example map function, to use in the example below.
map() { local f="$1"; shift; for i in "$@"; do "$f" "$i"; done; }

# Lambda function [λ], passed to the map function.
λ(){ echo "Lambda sees $1"; }; map λ "$@"

Curry

# Public: Curry function and argument into a new function.
#
# Takes a function with some arguments and partially applies them with a new
# function name.
#
# $1  : New function name
# $2- : Functions and arguments
#
# Example
#
#   curry log \
#         echo "LOGGING: "
#   log ERROR MESSAGE
#
# Returns output of expression.
curry() {
    declare s="$1() { "; shift
    for arg in "$@"; do printf -v s '%s %q' "${s}" "${arg}"; done
    eval "$s \"\$@\"; }"
}

# Example
lines() { printf '"%s"\n' "$@"; }
make_curry() { curry test_lines lines "line 1" "line 2"; }
make_curry; test_lines "line 3"  # outputs 3 lines

Compose

# Public: Compose partial functions into one
#
# Takes functions that compose to get a final result.
#
# $1  : New function name
# $2- : Functions and arguments, delimited by '^'
#
# Example
#
#   compose f \
#           multiply 3 \
#           ^ \
#           add 2 \
#           ^ \
#           multiply 2
#   f 15  # Prints "96" as ((15*2)+2)*3 = 96
#
# Returns output of expression if no error occurred.
compose()
{
    declare -r function_name=$1; shift

    IFS='' read -r -d '' body_template <<'EOF'
%s() {
    declare -i status
    declare output
    declare -a args

    %s

    printf '%%s' "${output}"
}
EOF

    IFS='' read -r -d '' function_template <<'EOF'
    output="$("${args[@]}")"
    status=$?
    if ((status != 0)); then
        echo "Failed running: ${args[*]}" 1>&2
        return $status
    fi
EOF

    declare body_content
    declare current_args

    for arg in "$@"; do
        if [[ $arg = '^' ]]; then
            printf -v body_content '    args=(%s "${output}")\n%s%s' \
                   "${current_args}" \
                   "${function_template}" \
                   "${body_content}"
            current_args=''
        else
            printf -v current_args '%s %q' "${current_args}" "${arg}"
        fi
    done
    printf -v body_content '    args=(%s "$@")\n%s%s' \
           "${current_args}" \
           "${function_template}" \
           "${body_content}"

    printf -v to_eval "${body_template}" "${function_name}" "${body_content}"
    eval "${to_eval}"
}

User interaction

press_key_to_continue

press_key_to_continue()
{
    read -n 1 -s -p "Press any key to continue"
}

Press y/n

confirmation_message_y_n()
{
    declare -r message="${1:-Are you sure?}"
    read -p "${message} (y/n): " -n 1 -r
    echo
    [[ $REPLY =~ ^[Yy]$ ]]
}

Useful functions (unorganized)

quoted

shell-quoted() {
    if (($#>1)); then
        printf '%q ' "${@:1: $(($#-1))}"
    fi
    printf '%q' "${@: -1}"
    printf '\n'
}

Convert 10/25m/2d/1w to seconds

multiplier_to_seconds()
{
    case "$1" in
        s|'')
            echo 1
            ;;
        m)
            echo 60
            ;;
        h)
            echo $((60*60))
            ;;
        d)
            echo $((60*60*24))
            ;;
        w)
            echo $((60*60*24*7))
            ;;
        M)
            echo $((60*60*24*30))
            ;;
        *)
            return 1
            ;;
    esac
}

to_seconds()
{
    declare -r all=$1
    declare -i multiplier=0
    if [[ $all =~ ^[1-9][0-9]*$ ]]; then
        echo "$all"
        return 0
    elif [[ $all =~ ^[1-9][0-9]*.$ ]]; then
        declare -r -i value=${all:0: -1}
        if ! multiplier=$(multiplier_to_seconds "${all: -1}"); then
            return 1
        fi

        echo $((value * multiplier))
    else
        return 1
    fi
}

Use custom IFS

use_ifs()
{
    declare -r old_ifs=$IFS
    IFS=${1:?}; shift
    "$@"
    declare -r -i status=$?
    IFS=$old_ifs
    return $status
}

awk wrappers

Parse access log, count status

access_log_status_counts()
{
    read -r -d '' awk_script << 'EOF'
BEGIN {
  m1_remote_ip=1
  m1_local_time=2
  m1_request=3
  m1_status=4
  m1_bytes_sent=5
  m1_referer=6
  m1_forwarded_for=7
}
{
  count = match($0,
                /^([0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}) - - \[([^\]]*)\] "([^"]*)" \(([0-9]+)\) ([0-9]+) "([^"]*)" "([^"]*)"/,
                matches)
  if (count != 0) {
    status=matches[m1_status]
    totals[status] = totals[status] + 1
  }
}

END {
  for (status in totals) {
    print(status ": " totals[status])
  }
}
EOF

    /bin/awk "${awk_script}" /lrtemp/logs/access.log
}

Field is one of

field_is_one_of()
{
    declare -r field=${1:?}; shift

    read -r -d '' awk_script << 'EOF'
BEGIN {
  IGNORECASE = 1
}
{
  field=$FIELD
  if (match(field, "VALUE")) {
    print
    next
  }
NEXTTEST
}
EOF

    read -r -d '' nexttest_template << 'EOF'
  if (match(field, "^VALUE$")) {
    print
    next
  }
NEXTTEST
EOF

    awk_script="${awk_script//FIELD/${field}}"

    local val nexttest
    for val in "$@"; do
        nexttest="${nexttest_template//VALUE/${val}}"
        awk_script="${awk_script//NEXTTEST/${nexttest}}"
    done
    awk_script="${awk_script//NEXTTEST/}"

    /bin/awk "${awk_script}"
}

Compare versions

version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }

Include

From PHP

get_php_var()
{
    declare -r var="$1"; shift
    declare -r -a requireds=("$@")

    local phpr req
    for req in "${requireds[@]}"; do
        printf -v phpr '%srequire_once "%s";\n' "${phpr}" "${req}"
    done

    printf -v phpr '%secho %s;\n' "${phpr}" "${var}"

    local from_php
    if ! from_php="$(php -derror_reporting=0 -ddisplay_errors=0 -r "${phpr}" 2>/dev/null)"; then
        return 1
    fi
    echo "${from_php}"
}

get_php_var 'MINIMUM_OBFUSCATON_FILESIZE' \
            /lr/c/inc/php/downloads.inc.php

Is program running

is_script_running()
{
    declare -r runner=${1:?}
    declare -r script=${2:?}

    pgrep -f "\b${runner}\b .*\b${script}\b" >/dev/null
}

script_running_count()
{
    declare -r runner=${1:?}
    declare -r script=${2:?}

    pgrep -fc "${runner} .*\b${script}\b"
}

is_program_running()
{
    declare -r runner=${1:?}

    pgrep -x "${runner}" >/dev/null
}

program_running_count()
{
    declare -r runner=${1:?}

    pgrep -xc "${runner}"
}

Output status limited by width

check_service()
{
    declare -r service=${1:?}
    declare -r -i service_width=${#service}
    declare -r -i width=$((cols - service_width - 1))

    if ! systemctl -q is-active "$1"; then
        printf '%s %*s' "${service}" "${width}" '[ FAILED]'
    else
        printf '%s %*s' "${service}" "${width}" '[SUCCESS]'
    fi
}

Tail to single line

static_tail()
{
    declare -i prev_len=0
    declare -i this_len=0
    declare -i cols="$(tput cols)"

    printf '\r'
    for ((i=0; i < cols; ++i)); do
        printf ' '
    done
    printf '\r'

    while IFS= read -r line; do
        this_len=${#line}
        printf '%s' "${line:0:$cols}"
        for ((i=this_len; i < prev_len && i < cols; ++i)); do
            printf ' '
        done
        printf '\r'
        prev_len=${this_len}
    done
    printf '\n'
}

Argument handling

Take from stdin or argument examples

commands that takes arguments

parse input by line, each line parsed into arguments
comment()
{
    declare -r -a cmd=(printf '<-- %s -->\n')
    if (($#)); then
        "${cmd[@]}" "$@"
        return $?
    fi
    declare -i status
    while read -r line; do
        eval set -- $line
        "${cmd[@]}" "$@"; status=$?
        if ((status)); then
            return $status
        fi
    done
}
parse input by line, each line a single argument
comment()
{
    declare -r -a cmd=(printf '<-- %s -->\n')
    if (($#)); then
        "${cmd[@]}" "$*"
        return $?
    fi
    declare -i status
    while read -r line; do
        "${cmd[@]}" "${line}"; status=$?
        if ((status)); then
            return $status
        fi
    done
}
parse input as single argument
comment()
{
    declare arg="$*"
    if ((! $#)); then
        if ! arg=$(</dev/stdin); then
            return 1
        fi
    fi
    printf '<-- %s -->\n' "${arg}"
}

command that takes stdin

to_upper()
{
    if ((! $#)); then
        tr '[:lower:]' '[:upper:]'
    else
        tr '[:lower:]' '[:upper:]' <<< "$*"
    fi
}

Take from stdin or argument if exists wrappers

arg_or_stdin_from_stdin_cmd

# Public: Wrapper to make function take arguments or stdin interactive
#
# Turn a command that takes stdin input into a command that takes arguments or
# stdin.
#
# $1 - Separator to follow command and args
# $* - Command and command args
# $? - Separator
# $* - Possible arguments
#
# Example
#
#   to_upper()
#   {
#       arg_or_stdin_from_stdin_cmd -- tr '[:lower:]' '[:upper:]' -- "$@"
#   }
arg_or_stdin_from_stdin_cmd()
{
    declare -a cmd
    local -r separator="${1:?}"; shift
    local -i found_end
    local arg
    while [[ $# -ne 0 ]]; do
        arg="$1"; shift
        if [[ $arg = "${separator}" ]]; then
            found_end=1
            break
        fi
        cmd+=("${arg}")
    done
    if ((found_end != 1)); then
        echo "take_arg_or_stdin: Missing \";\"." 1>&2
        return 1
    fi

    if [[ $# -eq 0 ]]; then
        "${cmd[@]}"
    else
        "${cmd[@]}" <<< "$*"
    fi
}

arg_or_interact_from_stdin_cmd

# Public: Wrapper to make function take arguments or stdin interactive
#
# Turn a command that takes stdin input and make a command that takes arguments
# or stdin.
#
# $1 - Separator to follow command and args
# $* - Command and command args
# $? - Separator
# $* - Possible arguments
#
# Example
#
#   to_upper()
#   {
#       arg_or_interact_from_stdin_cmd -- tr '[:lower:]' '[:upper:]' -- "$@"
#   }
arg_or_interact_from_stdin_cmd()
{
    declare -a cmd
    declare -r separator="${1:?}"; shift
    local arg
    local -i found_end
    while [[ $# -ne 0 ]]; do
        arg="$1"; shift
        if [[ $arg = "${separator}" ]]; then
            found_end=1
            break
        fi
        cmd+=("${arg}")
    done
    if ((found_end != 1)); then
        echo "take_arg_or_stdin: Missing \";\"." 1>&2
        return 1
    fi

    if [[ $# -eq 0 ]]; then
        while read -r line; do
            "${cmd[@]}" <<< "${line}"
        done
    else
        "${cmd[@]}" <<< "$*"
    fi
}

arg_or_interact_from_arg_cmd

# Public: Wrapper to make function take arguments or stdin interactive
#
# Turn a command that takes additional arguments into one that takes arguments
# or stdin line by line.
#
# $1 - Separator to follow command and args
# $* - Command and command args
# $? - Separator
# $* - Possible arguments
#
# Examples
#
#   into_lines()
#   {
#       arg_or_interact_from_arg_cmd -- printf '%s\n' -- "$@"
#   }
arg_or_interact_from_arg_cmd()
{
    declare -a cmd
    declare -r separator="${1:?}"; shift
    local arg
    local -i found_end
    while [[ $# -ne 0 ]]; do
        arg="$1"; shift
        if [[ $arg = "${separator}" ]]; then
            found_end=1
            break
        fi
        cmd+=("${arg}")
    done
    if ((found_end != 1)); then
        echo "take_arg_or_stdin: Missing \";\"." 1>&2
        return 1
    fi

    if [[ $# -eq 0 ]]; then
        while read -r line; do
            eval set -- $line
            "${cmd[@]}" "$@"
        done
    else
        "${cmd[@]}" "$@"
    fi
}

Does system use systemd?

is_init_systemd() { [[ "$(ps --pid 1 -o comm=)" = "systemd" ]]; }

Watch load average (loadavg)

declare -r -i cpu_count="$(cat /proc/cpuinfo  | grep '^processor[^:]: [0-9]\+' | wc -l)"
print_load_avg_percent()
{
    declare -r -i load_avg_x100="$(awk '{print ($1 * 100)}' /proc/loadavg)"
    echo "$((load_avg_x100 / cpu_count))"
}

declare -r -i high_load_percent=98
loadavg_percent="$(print_load_avg_percent)"
if ((loadavg_percent < high_load_percent)); then
    # ...
fi

Convert video to web video

#!/bin/bash

declare -r source_dir="/mnt/media/Video/Astro Fighter Sunred"
declare -r target_dir="/mnt/media/Video/TV/Astro Fighter Sunred"

basename.() { basename "$1" | cut -d. -f1; }

extract()
{
    declare -r src_file="${1:?}"
    declare -r tgt_file="${target_dir}/$(basename. "${src_file}").m4v"

    if [[ -e "${tgt_file}" ]]; then
        return 0
    fi

    # rm -f "${tgt_file}"

    declare -a args=(
        -i "${src_file}"

        # Uses  https://trac.ffmpeg.org/wiki/Encode/H.264

        # Use baseline profile for x264
        -profile:v 'baseline'

        -pix_fmt yuv420p

        # Constant rate factor
        -crf 35.0

        # web video
        -c:v libx264
        -x264-params rc_lookahead=30:keyint=500

        # web audio
        -c:a libfdk_aac
        -b:a 128k

        -c:s mov_text

        "${tgt_file}"
    )

    echo ffmpeg "${args[@]}"
    ffmpeg "${args[@]}" &>/dev/null
}

main()
{
    local files="$(find "${source_dir}" -maxdepth 1 -type f -name '*.mkv' | sort)"

    echo "File list:"
    while IFS= read -r file; do
        echo "${file}"
    done <<< "${files}"

    echo "STARTING..."
    while IFS= read -r file; do
        echo "${file}"
        if ! extract "${file}"; then
            echo FAILED
        fi
    done <<< "${files}"

    # while IFS= read -r -d $'\0' file; do
    # done < <(find "${source_dir}" -maxdepth 1 -type f -name '*.mkv' -print0 | sort -z)
}

main

stdin as argument

stdin_to_arg()
{
    local input
    if ! input=$(</dev/stdin); then
        return 1
    fi

    "$@" "${input}"
}

less tail

less_tail()
{
  less +F "$@"
}

Get all child processes

get_all_child_processes()
{
    declare -r -i pid="$1"

    local children
    if children="$(pgrep -P "$pid")"; then
        for child in ${children}; do
            printf '%s\n' ${child}
            get_all_child_processes "${child}"
        done
    fi
}

Kill process and all children

is_pid_alive()
{
    kill -s 0 "${1:?}" 2>/dev/null
}

nice_kill()
{
    declare -r -i pid=${1:?}

    kill_process_tree "${pid}"
    sleep 1
    if is_pid_alive "${pid}"; then
        kill_process_tree "${pid}" KILL
        kill -9 "${pid}"
    fi
}

kill_process_tree()
{
    declare -r -i pid="${1:?}"

    local children
    if children="$(pgrep -P "${pid}")"; then
        for child in ${children}; do
            kill_process_tree "${child}"
        done
    fi

    kill -9 "${pid}"
}

tick at interval

# Public: Setup a command to run every certain number of seconds.
#
# Causes execution to stop while a command is run at an interval.  Use with an
# alarm safe sleep if using along with the sleep command.  Only one tick is
# supported at a time.  Use stop_tick to stop.
#
# WARNING: Signals are only handled by bash when it runs.  That means that a
# handler will only fire after a command executes and returns execution to bash,
# which prevents traps from being useful as watchdogs.  Any command that could
# take a long time needs to be run as a subprocess, using wait to catch the
# signal.
#
# $1 - Interval in seconds to run command.  Takes float as well.
# $2 - Command to run.  Uses trap, so command can contain subshell to execute
#      each time if single quotes are used.  First tick happens after interval,
#      not immediately.
# $3 - OPTIONAL  Signal to use.  Default is USR1.
#
# Examples
#
#   start_tick 1 'echo "$(date +%s)"'
#   alarm_safe_sleep 3
#   echo "STOPPING"
#   date
#   stop_tick
#   alarm_safe_sleep 2
#   date
#
#   Example output:
#     1496164932
#     1496164933
#     STOPPING
#     Tue May 30 12:22:14 CDT 2017
#     Tue May 30 12:22:16 CDT 2017
declare -i tick_pid=0
start_tick()
{
    declare -r rate=${1:?}
    declare -r handler=${2:?}
    declare -r sig=${3:-USR1}

    trap "${handler}" "${sig}"

    {
        local -i start_tick_child_stop=0
        trap "start_tick_child_stop=1" USR1
        sleep "$rate"
        while ((start_tick_child_stop==0)) &&
                  kill -s "${sig}" $$ &>/dev/null
        do
            sleep "$rate"
        done
    } &
    tick_pid=$!
}

# Public: Stop tick initialized in start_tick.
#
# $1 - OPTIONAL  Signal to cancel.  Should be same used with start_tick.
stop_tick()
{
    declare -r sig=${1:-USR1}

    trap - "${sig}"

    if ((tick_pid > 0)); then
        kill -s USR1 $tick_pid
        tick_pid=0
    fi
}

is_tcp_port_open

# Public: Checks if given TCP port is taken.
#
# $1 - IP address
# $2 - Port number
#
# Returns 0 if port is in use.
is_tcp_port_open()
{
    declare -r ip="${1:?}"
    declare -r port="${2:?}"
    lsof -i "tcp@${ip}:${port}" > /dev/null
}

# Public: Waits for port to become available for use.
#
# $1 - IP address
# $2 - Port number
# $3 - Max number of seconds to wait before giving up.
#
# Returns 0 if port became available before timeout reached.
wait_for_port_to_become_available()
{
    declare -r ip="${1:?}"
    declare -r -i port="${2:?}"
    declare -r -i timeout_seconds="${3:?}"

    for ((i = 0; i < (timeout_seconds * 4); ++i)); do
        if ! is_tcp_port_open "${ip}" "${port}"; then
            return 0
        fi
        sleep 0.25
    done
    return 1
}

Iterate with pause inbetween

iterate_with_pause_inbetween()
{
    declare -r -i times=${1:?}
    declare -r pause_seconds=${2:?}
    shift 2  # Command follows

    if ((times==0)); then
        return
    fi

    for ((i=0; i<times-1; ++i)); do
        "$@"
        sleep "${pause_seconds}"
    done
    "$@"
}

Wait for process with timeout

example()
{
    # Start script in background and wait for completion
    ${PHP_LOCATION} ${SCRIPT_LOCATION} &
    declare -r -i child_pid=$!

    if ! wait_with_timeout "${child_pid}" "${min_wait_seconds}"; then
        # Script still running, stop it
        nice_kill "${child_pid}"
        sleep 30
    else
        # Script completed.  Wait a random number of seconds
        sleep $(( RANDOM % 541 + 60 ))
    fi
}

nice_kill()
{
    declare -r -i pid=${1:?}

    kill "${pid}"
    sleep 1
    if is_pid_alive "${pid}"; then
        kill -9 "${pid}"
    fi
}

is_pid_alive()
{
    kill -s 0 "${1:?}" 2>/dev/null
}

wait_with_timeout()
{
    declare -r -i pid=${1:?}
    declare -r -i timeout_seconds=${2:?}

    # Wait for process to complete
    for ((i = 0; i < timeout_seconds; ++i)); do
        sleep 1
        if ! is_pid_alive "${pid}"; then
            return 0
        fi
    done
    return 1
}

rsync with password

log_stderr() { cat <<< "$@" 1>&2; }

with_temp_file()
{
    declare to_call="${1:?}"
    shift
    local path
    if ! path="$(mktemp)"; then
        return 1
    fi

    trap "rm -f ${tmp_files[*]}" EXIT
    "${to_call}" "$@" "${path}"
    declare -r -i result=$?

    /bin/rm "${path}"
    trap - EXIT

    return "${result}"
}

rsync_with_password_from_remote()
{
    declare -r remote_host="${1:?}"
    declare -r remote_user="${2:?}"
    declare -r remote_password="${3:?}"
    declare -r remote_path="${4:?}"
    declare -r local_path="${5:?}"

    with_temp_file \
        rsync_with_password_inner \
        "${remote_user}" \
        "${remote_password}" \
        "${remote_host:?}:${remote_path}" \
        "${local_path}"
}

rsync_with_password_to_remote()
{
    declare -r remote_host="${1:?}"
    declare -r remote_user="${2:?}"
    declare -r remote_password="${3:?}"
    declare -r local_path="${4:?}"
    declare -r remote_path="${5:?}"

    with_temp_file \
        rsync_with_password_inner \
        "${remote_user}" \
        "${remote_password}" \
        "${local_path}" \
        "${remote_host:?}:${remote_path}"
}

rsync_with_password_inner()
{
    declare -r remote_user="${1:?}"
    declare -r remote_password="${2:?}"
    declare -r from_path="${3:?}"
    declare -r to_path="${4:?}"
    declare -r password_file="${5:?}"

    declare -a rsync_command=(
        rsync
        --rsh="sshpass -f\"${password_file}\" ssh -l \"${remote_user:?}\""
        -av
        "${from_path:?}"
        "${to_path:?}"
    )

    log_stderr "Rsync command: ${rsync_command[*]}"
    printf '%s\n' "${remote_password}" > "${password_file}"
    "${rsync_command[@]}"
    declare -i status=$?

    if [[ $status -ne 0 ]]; then
        log_stderr "Rsync failure: $status"
        return 1
    fi
    return 0
}

File size parsing

human_readable_to_bytes()
{
    declare -r human_readable_size="${1:?}"
    if ! numfmt --from=auto <<< "${human_readable_size}"; then
        return 1
    fi
}

bytes_to_mebibytes()
{
    declare -r bytes="${1:?}"
    if ! numfmt --to=iec-i --suffix=B --format='%.2f' <<< "${bytes}"; then
        return 1
    fi
}

size_to_human_readable()
{
    if ! numfmt --to=iec-i --from=auto "$@"; then
        return 1
    fi
}

Check if pid/process is alive

is_pid_alive()
{
    kill -s 0 "${1:?}" 2>/dev/null
}

Nicer stat

print_info_for_path()
{
    declare -r info="$1"
    declare -r path="$2"
    local format
    declare -i rights

    if ! [[ -e ${path} ]]; then
        return 1
    fi

    case "${info}" in
        size|bytes)
            echo $(($(stat -L -c "${format}" "${path}")))
            return $?
            ;;
        owner_access_rights)
            if ! rights="0$(stat -L -c "%a" "${path}")"; then
                return 1
            fi
            printf '%o' $((rights & 0700 / 0100))
            return 0
            ;;
        group_access_rights)
            if ! rights="0$(stat -L -c "%a" "${path}")"; then
                return 1
            fi
            printf '%o' $((rights & 070 / 010))
            return 0
            ;;
        global_access_rights)
            if ! rights="0$(stat -L -c "%a" "${path}")"; then
                return 1
            fi
            printf '%o' $((rights & 07))
            return 0
            ;;
        access_rights)
            # Prepend 0 for octal
            format='0%a'
            ;;
        inode)
            format='%i'
            ;;
        mtime)
            format='%y'
            ;;
        mtime_seconds)
            format='%Y'
            ;;
        mount_point)
            format='%m'
            ;;
        optimal_transfer_size)
            format='%o'
            ;;
        owner)
            format='%G'
            ;;
        user)
            format='%U'
            ;;
        *)
            return 2
    esac

    stat -L -c "${format}" "${path}"
}

Write permissions

user_can_write_to_file() {
    declare -r user="${1:?missing user}"
    declare -r file="${2:?missing file}"

    if ! [[ -e ${file} ]]; then
        user_can_write_to_file "${user}" "$(dirname "${file}")"
        return $?
    fi

    # Use -L to get information about the target of a symlink,
    # not the link itself, as pointed out in the comments
    declare -r -a info=( $(stat -L -c "%a %G %U" "$file") )
    declare -r -i permissions=0${info[0]}
    declare -r group_name=${info[1]}
    declare owner_name=${info[2]}

    if (((permissions & 0002) != 0)); then
        # Everyone has write access
        return 0
    fi

    if (((permissions & 0020) != 0)); then
        # Is user in group with access?
        for g in $(groups "$user"); do
            if [[ $group_name == "$g" ]]; then
                return 0
            fi
        done
    elif (((permissions & 0200) != 0)); then
        if [[ $user == "$owner_name" ]]; then
            return 0
        fi
    fi
    return 1
}

get_free_spaces

print_free_bytes_on_path()
{
    declare path="${1:?}"
    if ! [[ -e ${path} ]]; then
        return 1
    fi

    echo $(($(stat -fc "%f*%S" "${path}")))
}

print_free_kilobytes_on_path()
{
    declare path="${1:?}"
    if ! [[ -e ${path} ]]; then
        return 1
    fi

    echo $(($(stat -fc "%f*%S" "${path}") / 1024))
}

is_a_function (function exists)

is_a_function() { [[  $(type -t "$1") == "function" ]]; }

Log functions

readonly this_script_filename="$(basename "${BASH_SOURCE[0]}")"

log()
{
    cat <<< "$@"
    printf "%s %s >>> %s\n" "$(date "+%m/%d/%Y %H:%M:%S")" "${this_script_filename}" "$*" >> /var/log/linkright.log
    logger -p user.notice -t "${this_script_filename}" "$@"
}

err()
{
    cat <<< "$@" 1>&2
    printf "%s %s >>> %s\n" "$(date "+%m/%d/%Y %H:%M:%S")" "${this_script_filename}" "$*" >> /var/log/linkright.log
    logger -p user.error -t "${this_script_filename}" "$@"
}

log "writing to stdout"
err "writing to stderr"

Join array into string

# Public: Join array into string with a delimiter.
#
# $1 - Delimiter
# $2+ - Items to join
#
# Example
#
#   print_join_array ", " $(seq 1 10)
#   Output:  1, 2, 3, 4, 5, 6, 7, 8, 9, 10
#
# Outputs joined string to stdout.
print_join_array()
{
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

strindex

a="The cat sat on the mat"
b=cat
strindex() { 
  x="${1%%$2*}"
  [[ $x = $1 ]] && echo -1 || echo ${#x}
}
strindex "$a" "$b"   # prints 4
strindex "$a" foo    # prints -1

Check for prefix

has_prefix()
{
    declare -r prefix="$1"
    declare -r str="$2"
    [[ "$str" == "$prefix"* ]]
}

Strip prefix

Will echo original value if prefix does not match.

strip_matching_prefix()
{
    declare -r prefix="$1"
    declare -r str="$2"
    echo ${str#$prefix}
}

chr

chr() {
  printf \\$(printf '%03o' $1)
}

ord

ord() {
  printf '%d' "'$1"
}

Wrap long lines of bash code in bash

w=80
while IFS= read -r line; do
    if ((${#line} > w)); then
        ind=''
        while IFS= read -r inner; do
            printf '%s%s' "$ind" "${inner}"
            printf -v ind ' \\\n  '
        done < <(fold -sw$w <<< "$line")
        echo
    else
        echo "${line}"
    fi
done <<< "$lines"

Colors

NameCode
Black0;30
Blue0;34
Green0;32
Cyan0;36
Red0;31
Purple0;35
Brown0;33
Light Gray0;37
Dark Gray1;30
Light Blue1;34
Light Green1;32
Light Cyan1;36
Light Red1;31
Light Purple1;35
Yellow1;33
White1;37

Enclose with:

Start
\[\033[
End
\]

Use with grep

Set env var GREP_COLORS with the code and pass “–color=always” as an argument.

Example:

tail -f myfwlog | GREP_COLORS='ms=01;36' egrep --color=always 'ssh|$' | GREP_COLORS='ms=01;31' egrep -i --color=always 'drop|deny|$'

Emacs

Search and replace backticks

You still need to watch out for:

  • Backquotes
    • Backquoted backticks
      • SQL statements
      • Nested statements
%s/`\(.*?\)`/$(\1)/gc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment