Cyberops bash notes

The command line is all of the various non-GUI executables installed wtih an OS, including the built-ins, executables, keywords, and scripting capabilities available from the shell–the command line interface. Its how commands were given to a computer before GUIs.

Basics

When several words appear on the command line, bash assumes that the first word is the name of the program to run and the remaining words are command arguments.

Commands, Args, Built-ins, and Keywords

The commands that you can run are either files, built-ins, or keywords:

  • files are executable programs
  • built-ins are part of the shell, such as pwd or cd
  • keywords are part of the language of the shell, such as if

Commands

$ type -t cat # identify what the argument is
file

# compgen lists what commands (-c), keywords (-k), and built-ins (-b) are available
$ compgen -k
if
then
else
elif
fi
case
esac
...

STDIN, STDOUT, STDERR

Every program is a process, and every process has 3 distinct file descriptors:

  • stdin: File descriptor 0. Default source for input to a program
  • stdout: File descriptor 1. Default place for sending output (the console)
  • stderr: File descriptor 2. Where error messages are written

Redirection and piping

Redirection is when you change the input and outputs of a program without modifying the program.

OperatorDescriptionExample
>Sends the output of the value on the left to the value on the right.$ ls -la > listing.out
<Sends the value on the right to the STDIN of the value on the left.$ program < input.txt
2>Redirect STDERR messages to the value on the right.$ cp -r /etc/a /etc/b 2> err.msg
2>&1, &>Send output to STDOUT and STDERR. Place thisXXXXXXXXXXX

The following command sends input.txt to the STDIN of program, and sends the output to output.out:

$ program < input.txt > output.out

To distinguish between STDOUT and STDERR, use the file descriptor 2>. The following command redirects error messages to err.msgs:

$ program 2> err.msgs

Or, combine all redirection methods:

$ program < input.txt > output.out 2> err.msgs

Combined redirect: To send STDERR to the same location as STDOUT, combine the error messages with the standard output:

$ program < input.txt > output.out 2>&1
# shorthand
$ program < input.txt &> output.out

The previous command sends input.txt to program, then sends the STDOUT and STDERR to output.out. The shorthand is more clear.

Commonly, you can discard STDOUT by sending it to /dev/null:

$ program < input.txt > /dev/null

The tee command sends output to STDOUT and the file that follows the command:

$ program < input.txt | tee results.out # the -a option allows tee to append to the file

To append to a file, use the >> operator:

$ program < input.txt >> appended.file

To append STDOUT and STDERR, use &>>:

$ program < input.txt &>> appended.file

Running commands in the background

Use the & operator at the end of the command to run it in the background:

$ ping 10.20.30.40 > ping.log &

When you run a task in the background, send both STDOUT and STDERR so the task doesn’t log everything to the console:

$ ping 10.20.30.40 &> ping.log &

To bring a job back to the foreground, use jobs to list running tasks, then use fg with the corresponding task number:

$ ping 192.168.10.56 &> ping.log &
[1] 7452
$ jobs
[1]+  Running                 ping 192.168.10.56 &> ping.log &
$ fg 1
ping 192.168.10.56 &> ping.log
^C

Stop a running job with Ctrl+z, and send it to the background with bg:

$ ping 192.168.10.56 &> ping.log &
[1] 7572
$ fg 1
ping 192.168.10.56 &> ping.log
^Z
[1]+  Stopped                 ping 192.168.10.56 &> ping.log
$ bg 1
[1]+ ping 192.168.10.56 &> ping.log &
$ fg 1
ping 192.168.10.56 &> ping.log
^C

Scripts

Scripts contain more than one shell commands. Use chmod 755 scriptname to make it executable. Begin each bash script with one of the following lines:

#!/bin/bash -
#!/usr/bin/env bash

The second option uses env to look up the location of the bash executable. This is supposed to solve portability problems.

Bash primer

# output
echo 'word'
printf 'word\n'

Variables

VARNAME=varvaluetext
# to get the var, prepend a $
echo $VARNAME

# single quotes preseve space
VARNAME='this is the variable value'
# double quotes allow substitutions
NEWVAR="this is $VARNAME"

# store value of shell command
PRINTDIR=$(pwd)
echo $PRINTDIR

Positional parameters

$@ # stores all args and makes them avaialable in array
$0 # name of script
$1 # first param
$2 # second param
$# # number of params passed
$? # value returned by previous command

for var in $@
do
    echo "Hello, $var."
done

# 'for' loops over the arguments by default, as long as you don't ad the 'in x' part
for ARG
do
    echo here is an argument: $ARG
done

Input

# read accepts input from stdin and stores in var
read INPUTVAR
echo $INPUTVAR

Conditionals

$? stores the return code for a command. Use it directly after the command runs.

# if a command returns a 0, then do x, y, and z
if <command>
then
    <something>
else
    <something else>
fi

Tests

OperatorUse case
-dTest if directory exists
-eTest if file exists
-rTest if file exists and is readable
-wTest if file exists and is writeable
-xTest if file exists and is executable
-eqTest if numbers are equal
-gtTest if first number is greater than second number
-ltTest if first number is less than second number
# [[ ]] is the compound command
if [[ -e $FILENAME ]]
then
    echo $FILENAME existsd
fi

# if VAR has a value
if [[ $VAR ]]; then
    # do something
fi

# numbers
if [[ $VAL -lt $MIN ]]
then
    echo "$VAL is too small"
fi

# for arithmetic operations, or when comparing numbers with 
# the < or > signs, use (( ))
if (( VAL > 2))
then
    echo "value $VAL is too small
fi

if (( $? )) ; then echo "previous command failed" ; fi

Loops

# while loop
i=0
while (( i < 100 ))
do
    echo $i
    let i++
done

# for loop
for ((i=0; i < 100; i++))
do
    echo $i
done

Functions

Function arguments are accessible with $1, $2,…. You have to store the script args in variables to use them in functions

  • $# is the number of args passed to the func
  • $0 is still the name of the script

Functions return a status. To return other values, set a variable to hold the value.

function myfunc()
{
    # do something
}

# call functions like a shell command
myfunc arg1 arg2 arg3

# both of these functions run in a subshell
myfunc arg1 arg1 | next-step | etc
RETVAL=$(myfunc arg1 arg1)

Pattern matching

*   # match any number of any character
?   # match a single character ex. source.? returns source.c but not source.cpp
[ ] # match any of the characters within the brackets

Bracket matching

[0-9]  # range
[!cat] # match anything other than the chars in the brackets
[^cat] # match anything other than the chars in the brackets
*[[:punct:]]jpg # match any file with any number of characters followed by a 
                # punctuation character, then 'jpg'

more patterns if you turn on shell’s extglob option

Character classDescription
[:alnum:]Alphanumeric
[:alpha:]Alphabetic
[:ascii:]ASCII
[:blank:]Space and tab
[:ctrl:]Control characters
[:digit:]Number
[:graph:]Anything other than control characters and space
[:lower:]Lowercase
[:print:]Anything other than control characters
[:punct:]Puncuation
[:space:]Whitespace, including line breaks
[:upper:]Uppercase
[:word:]Letters, numbers, and underscore
[:xdigit:]Hexadecimal

Regex primer

In bash, regex are valid only when using the =~ comparison in the double-bracket ([[ ]]) compound command.

grep

Searches a file for a pattern and prints the line if there is a match:

grep <options> <pattern> <filenames>
grep -R -i 'password' /home

# egrep to extend syntax for ?, +, {, |, (, and )
OptionDescription
-cCount number of lines that match the pattern
-EEnable extended regex
-fRead from a file
-iIgnore case
-lPrint only filename and path where the pattern was found
-nPrint line number where pattern was found
-PEnable Perl regex (for shortcuts)
-qDo not output anything
-R, -rRecursively search subdirectories

Metacharacters

Escape with \ to treat characters without special meaning.

. # match any single character except newline
? # preceding char is optional, match zero or one time
* # preceding char is optional, match zero or more times
+ # match preceding char one or more times

Grouping

group with parentheses to treat characters as one item

egrep 'And be one (stranger|traveler), long I stood' frost.txt 
3    And be one traveler, long I stood

Brackets and character classes

[] define character classes and lists of acceptable characters:

ExampleDescription
[abc]Match only a, b, or c
[1-5]Match digits in range from 1 to 5
[a-zA-Z]Match lower or uppercase letter
[0-9 +-*/]Match any number or math symbol
[0-9a-fA-F]Match any hexadecmial digit
[1-475]Match numbers between 1 and 4, and digits 7 and 5

Shortcuts

Not supported by egrep, must use -P option:

ShortcutDescription
\sWhitespace
\SNot whitespace
\dDigit
\DNot digit
\wWord
\WNot word
\xHexidecimal number

Character classes

Only match single character, use + or * to find repetition, and has to be in brackets ([[:<char-class>:]]):

Character classDescription
[:alnum:]Alphanumeric
[:alpha:]Alphabetic
[:ctrl:]Control characters
[:digit:]Number
[:graph:]Anything other than control characters and space
[:lower:]Lowercase
[:print:]Anything other than control characters
[:punct:]Puncuation
[:space:]Whitespace, including line breaks
[:xdigit:]Hexadecimal

Back references

A back reference lets you reference a previous regex match. You have to close the regex in ( ), and then reference it later with a backslash and number.

The following back reference matches any HTML tag. The \1 means, “match again with whatever was matched in the preceding parentheses”:

egrep <([a-zA-Z]*)>.*</\1> file.txt

You can have more than one back reference, e.g. ()...\2, ()...\3, etc..

Quantifiers

T{5}   # T must appear 5 consecutive times
T{3,6} # T must appear 3 to 6 times
T{5,}  # T must appear 5 or more times

Anchors and word boundaries

^[1-5] # beginning anchor. Matching string must start with 1-5 as the first char
[1-5]$ # end anchor. Matching string must end with 1-5 as the first char

sort

Rearrange a text file into numerical or alpha order.
-r
descending order
-f
ignore case
-n
numerical ordering
-k
sort based on key in a line, delmited by whitespace by default
-o
write output to specified file
# sort by 3rd column
sort -k 3 filename.txt

# sort by chars 4 and 5 of field 2 (non-zero index)
sort -k 2.4,2.5 filename.txt

uniq

Filters out duplicate lines that occur adjacent to one another. Usually sort first:
-c
prints number of times line is repeated
-f
ignore the specified number of fields before comparison
-i
ignore case.

Sorting and arranging data

Look for extremes–things that occurred least or most frequently:

  • requests returning 404
  • reqs from single IP address returning 404
  • reqs returning 401 Use sort, head, tail commands at end of pipeline:
... | sort -k 2.1 -rn | head -15

arrays and associative arrays (bash 4.0+) and read

# -A is assoc. array for bash 4.0+
declare -A arrayName1 arrayName2

# lowercase a is a regular array
declare -a RA_key RA_val

# access el in array
${ar[index]}

# get key values already in assoc. array (key, val)--don't think
# that the '!' negates a value--it is part of the language
${!ar[@]}

# each variable for read gets assigned the corresponding var
# so, index gets the first word, xtra gets remaining. If there
# are no remaining, xtra is empty string.
while read index xtra
do
    # do stuff
done

get keys in assoc.array

for var in "${!array[@]}"
do
    # do something
done

formatting strings

# id is 15 chars long, left-justified (-15)
# %8 is 8 digits long
printf "%-15s %8d\n" "${id}" "{cnt[${id}]}"

local integers

# use the i to declare integers (4 here)
local -i i raw maxraw scale

double parentheses

# you don't need $ to indicate 'value of'
((result=(COUNT*i)/maxcount))

Data of interest

  • Logfiles in /var/log
  • Command history in $HISTFILE in .bash_history
  • Temp files
  • User data in /home
  • Browser history

Remote commands with SSH

Execute remote commands if the remote machine is running the SSH service:

ssh <hostname> command
ssh mycomputer ps

# redirect to file on local machine
ssh <hostname> ps > filename.out

# redirect on remote machine
#   backslash escapes > on local shell, passes to 
#   remote where it is executed)
ssh <hostname> ps \> /tmp/ps.out

# run local script on remote system.
#   This runs the bash command on the remote and 
#   passes that command the lines of script.sh
ssh <hostname> bash < ./script.sh

Gather Linux logs

# ${} is parameter expansion, which retrieves the value of a variable
tar -czf ${HOSTNAME}_logs.tar.gz /var/logs
  • c creates archive file
  • z zips the file
  • f specifies name for output file

Important log files:

  • /var/log/apache2/
  • /var/log/auth.log
  • /var/log/kern.log
  • /var/log/messages
  • /var/log/syslog

Get info about log files from syslog.conf or rsyslog.conf.

Gather system info

Linux CommandMSWin BashPurpose
uname -auname -aO.S. version etc
cat /proc/cpuinfosysteminfosystem hardware and related info
ifconfigipconfigNetwork interface information
ip routeroute printrouting table
arp -aarp -aARP table
netstat -anetstat -anetwork connections
mountnet sharemounted disks
ps -etasklistrunning processes

readarray

readarray reads from STDIN and reads until it hits a newline or EOF. Here is how you read from a file:

readarray INPUTARRAY

for LINE in "${INPUTARRAY[@]}"; do
    # do work
done

# read contents of file into VAR array. -t removes trailing \n
readarry -t VAR < file.txt

String slicing

# '#' means match from the left (like comments)
# '%' means match from the right (like 5%, in general math) 

# removes everything to the right of STRING, including "|"
VAR=${STRING%%|*}

# removes first match to left of STRING, including "|"
VAR=${STRING#*|}

# removes SUBSTRING
VAR=${STRING//SUBSTRING/}

Searching the filesystem

# find searchs provided dir and subdir
# name searches for file name that matches pattern
find /home -name '*password*'
find /home -name '.*'

# surpress errors
find /home -name '*password*' 2>/dev/null

# file > 5GB
find /home -size +5G

# find 5 largest files in filesystem, supressing errors
ls / -R -s 2>/dev/null | sort -n -r | head -5

# modified time searches
find /home -mmin -5         # modified less than 5 mins ago
find /home -mtime -1        # modified less than 24 hrs ago
find /home -mtime +2        # modified more than 2 days ago
find /home -atime -1        # modified less than 24 hours ago

# exec option executes the command and replaces '{}' with the filepath
# search for all files in /home that were accessed less than 24 
# hours ago, and copy them to the pwd
find /home -type f -atime -1 -exec cp '{}' ./ \;    # '\;' ends the exec clause, not separate bash commands
                                                    # '{}' expands to the path of each result found in /home

# content search

# search home and subdirs for the string 'password'
grep -r -i /home -e 'password'  # -e means use regex

# find and grep. get all files in /home with password in the title and copy to pwd
find /home -type -f -exec grep 'password' '{}' \; -exec cp '{}' . \;


# search by file type

find in a loop

find $DIR <option> -type f | while read FN; do
    file $FN | egrep -q $CASEMATCH "$PATTERN"
    if (($? == 0)); then # found one
        echo $FN
        if [[ $COPY ]]; then
            cp -p $FN $DESTDIR
        fi
    fi
done

getops CLI parser

# while loop that uses getops builtin
# 'c:irR' are the accepted options. ":" means "c" accepts an arg
# opt is user-provided variable that shell stores each option
#   in for the loop
# OPTARG is the arg currently in process
# OPTIND is the next arg
while getopts 'c:irR' opt; do
    case "${opt}" in
    c) # copy found files to specified directory
        COPY=YES
        DESTDIR="$OPTARG"
        ;;
    i) # ignore u/l case differences in search
        CASEMATCH='-i'
        ;;
    [Rr]) # recursive, accept upper or lowercase
        unset DEEPORNOT ;;
    *)  # matches anything--unknown/unsupported option
        # error mesg will com from getopts, so just exit
        exit 2 ;;
    esac
done
# shift resets parsed args. If 3 args were read, this resets the arg count of the next arg to be processed(OPTIND) from $4 to $1.
# getops tracks args with OPTIND-it refers to the next arg to be processed
shift $((OPTIND - 1))

default values

# :- means if PATTERN isn't set, set to 'PDF document'
# set STARTDIR to '.'
PATTERN=${1:-PDF document} # use 1st arg, but if not set, use 'PDF document'
STARTDIR=${2:-.} # use the 2nd arg, but if not set, use cwd

Message digest value

$ sha1sum cmd.txt 
a5376365573cea36944c5fafe6793101e096a632  cmd.txt

Transferring data

# can add a similar line to end of collection scripts
scp info.tar.gz user@hostname:/home/path/to/dest

Uppercase to lowercase

Use the tr (translate) command to change the case of a character set:

uppercase=$(echo $var | tr '[a-z]' '[A-Z]')

lowercase=$(echo $var | tr '[A-Z]' '[a-z]')

Another option is the typeset command. You can use the -u and -l options to ensure a variable always evaluates in upper or lowercase, respectively:

$ typeset -u my_name
$ my_name='Sally'
$ echo $my_name 
SALLY


$ typeset -l last_name
$ last_name='Ride'
$ echo $last_name 
ride

You must set the typset value first.