it:ad:bash
Summary
Basic Linux equivalents of DOS commands:
# make dir mkdir #list files ls #sort sort < STUFF #search # grep is a command-line utility for searching plain-text data sets for lines that match a regular expression grep LINE STUFF | sort | uniq # remove directory rm # help on any command help mkdir
Good to Know
* no space on either side of =
when assigning variables (A=3, not A = 3)
* Expansion of variables only occurs within double quotes (not single quotes)
* Vars in strings can be $x
or ${x}
Dependencies
# figure out where Bash is located by finding out what # and you'll get '/bin/bash' back. # this will inform what to put after the She Bang.
Creating Bash Script Files
# create an empty file: touch spike.sh # make the file executable: ch +x spike.sh # ShBang The first two lines of the script must be: <sxh bash> #! /bin/bash
Invoking Script
# invoke it in current directory (so that it doesn't have to go search for it via PATH) ./spike.sh
Parsing
Parsing - Order
Like many script languages, it reads the stream from top to bottom, rather than parsing everything before it gets started.
So you have to define your entry points (eg: Main) at the bottom, and probably your first method defined will end being one called Trace
…
Parsing - Line Endings
Semi-colons can be used to disambiguate.
See how it's sometimes used to put for
/next
& do
on the same line.
for ((i = 0 ; i <= 1000 ; i++));do ... next
Parsing - Tokens
This is Hugely important to decrease your frustration. Bash is OLD. Therefore unforgiving in its parsing.
- Bash word splits by
white spaces
s,[
and a few other ones. Unless within quotes. - When ever it sees a $ followed by a word, it will attempt to replace that token with the referenced value (sometimes referred to as “Parameter Substitution” or “Expanding Variables”).
- the $ goes to the first “token invalid” symbol. If you need to disambiguate
$myVar
, you can wrap it in curlies${myVar}
(whether inside or outside of quotes). - Double quotes are parsed for variable expansion.
- Single quotes are literal & left alone, whether there is a $ or not.
- As single spaces outside of quotes are treated as token delineators you have to REALLY pay attention to how it thinks:
h="hello";w="world";
r=$h $w
# a is assigned to c, terminates at the space, then the processor tries to run the word world (and fails with command not found)r= $h $w
# nothing is assigned to c, then it tries to find the command 'hello' (failing) and then the command 'world' (failing again)r =$h $w
# r is tried to be executed, but not yet assigned or a commandname (so fails). Then assigns h to nothing. and tries to run the word “world” (fails)r = $h $w
# more of the samer="$h $w"
# FINALLY WORKS: “hello world” is assigned to cr="${h}ing the${w}"
Use curlies to dismabiguate so that it doesn't try to expand 'aing' and it fails
- Round brackets:
( scope ) # do me first
- Note: a source of annoyance as
(
)
tokens appear similar but are treated as different words/tokens than((
, which is how “math scopes” are defined.
$( command ) # expand me
(( expression )) # calc me
- $1) # calc me, then expand me with the result of the calc.
- This has a place inside of quotes “The count is $2)”
- but fails outside of quotes or assignments as you're asking the processor to execute a command that you're saying is named as a number…
[
and]
is a deprecated version of[[
and]]
* questions I have:i=10;((i=i+10)) #why does this work (as opposed to using ((i=$i+10)) -- but I guess inside a scope it is expecting tokens only, versus text?)
- funny enough, whereas usually you have to leave no space before and after
=
, because it's in an arithmetic scope, as opposed to a command scope it ignores the space.
* Square brackets:
- as conditions: (eg: in
if
statements)[
and]
brackets are the deprecated shorthand for thetest
command – use[[
and]]
instead. - as array indexes:
a=(foo bar baz)
- It appears logical to think one can access arrays as follows
$a[*]
or$a[1]
- BUT you need the curly brackets to disambiguate the parser:
${a[*]}
and${a[1]}
<sxh bash> # works: A=3 # will be 3 B=“Hello World” # will be 'Hello World' C=“$B” # will be 'Hello World' D='$B' # will be '$B'
A =3 # WARN: A= 3 # WARN: A = 3 # WARN: </sxh>
The reason is:
Echoing
echo "Some text..."
Variables
By convention, variables in BASH are UPPERCASE. But lowercase will work fine. Variables are created without $. And then referenced later with $.
FOO="Bar"
Variables -- Global By Default
It's an oooold language, so it has primitive ideas.
For one, every variable, whether in a function or not is global by default.
Variables -- can be Localised
Globals are evil. So it's strongly recommended you local
wherever you can.
local X=3
Input Prompts
Prompts fill new variables (note, no $).
read -p "So a person walks into a ${FOO} and asks you what is your name: " NAME read -p "Waiting to Continue..."
Echoing Variables
Note that variables are disambiguated within strings using ${…}
echo "Hi ${NAME}!"
Logic
Logic is implemented using if/elif
(not elseif
!)/else/fi.
Note also that both if
AND elif
are followed by their own then
(but just to shake things up, the final else
does not…)
if ["$NAME" == "Brad"] then ... elif ["$NAME" == "Bob"] then ... else ... # close brackets by reversing the if: fi
String Comparisons
String comparisons are simpler than equality comparisons:
# string comparison == # not equal !=
Comparisons
Comparisons if IF/ELSE statements will remind you of how [IT/AD/Powershell/] does then:
-eq -ne -gt -ge -lt -le
As to when to use ==
or -eq
…
File Conditions
Often comparing against text, but also files, which have different comparison operators:
# -e $FILE - True if exists # -f $FILE - True if file # -d $FILE - True if directory # -w $FILE - True if file writable # -x $FILE - True if file executable # -u $FILE - True if user id set on file
Which you use as follows:
# Create a Variable (notice, no $) FILE ="spike.txt" # Is File Exist? if [-e "$FILE] ... fi
Case Handling
Each case clause is terminated with a closing bracket… And the last/default answer is defined as a '*'
case "$ANSWER" in # accept upper/lower case "Y" or "YES" # Notice the closing, but not opening bracket [yY] | [yY][eE][sS]) echo "Cool" [nN] | [nN][oO]) echo "Not Cool" *) echo "Please enter Y/YES or N/NO" esac
Looping
for NAME in $NAMES do echo "Hello $NAME" done
Looping on Files
# invoke ls, and get the output: FILES=$(ls *.txt) # iterate over it: for FILE in $FILES do echo "Renaming ${FILE} to new-${FILE}" mv $FILE NEW-$FILE done
Looping
While
LINE=1 while read -r CURRENT_LINE do echo "$LINE: $CURRENT_LINE" ((LINE++)) done < "./theinputfile.txt"
For/Next
# Actually, no such thing as `for`/`next` -- its `for`+`do`/`done`. # notice double brackets # notice ability to put do on same line by semi-colon # for ((i = 0 ; i <= 1000 ; i++));do # or: for ((i = 0 ; i <= 1000 ; i++)) do echo "Counter: $i" done
For/Do/Done Through Arrays
for outerloop in 1 2 3 4 5 do echo -n "Group $outerloop: " done # alternatively: for i in {0..3} do echo "Number: $i" done # stepping: for i in {0..20..5} do echo "Number: $i" done # through arrays: STUFF=('Foo' 'Bar') for book in "${STUFF[@]}"; do echo "Book: $book" done
Functions
# note note arguments defined in brackets function FOO() { echo "BAR" }
Function Parameters
Parameters are not named – they are positional (reminds me of IT:AD:Perl) starting with $1 (not $0).
# note note arguments defined in brackets function BAR() { # yet they are there. echo "FOO $1" }
You can get all of them (excluding $0, which is the script filename):
$@
You can assign to another argument and shift it (preserving the original array):
$ARGS=$@ #maybe like this: $ARGS=shift $@
You can print all the arguments (for logging) as follows:
echo "$*"
Function - Returns
By default all vars are global.
AND Functions don't have Returns.
That doesn't leave many options to program in an productive manner.
Using StdOut
One option is to output to stdout and the caller uses command substitution to capture the value in a variable.
function S1(){ local FOO=3 # output to stream within execution context echo $FOO } #Eval the function and capture it's LAST output. RESULT=$(S1)
But warning: if you have two echo's (eg: debugging to self?) you'll get both…probably not what you were expecting
function S1(){ local FOO=3 # output to stream within execution context echo "A" echo $FOO } # Eval the function and capture it's LAST output (but it will be A, then newline then 3... Not what you expected. RESULT=$(S1)
Diagnostics
Due to the above issue, makes sense to know how to redirect echo
statements from stdout
to stderr
:
# redirect stdout (1) to stderr (2): echo "This Is Error" 1>&2
But definately improved with flags:
Put this on the first line: if [[ "debug" == "$1" ]]; then echo "Note: Diagnostics Echoing=ON" shift DIAGNOSTICS=yes fi ... [[ -n "$DIAGNOSTICS" ]] && echo "Debug..." 1>&2 <sxh> ## Piping Output to a File <sxh bash> # create file to write to first: touch "output.txt" # pipe output: echo "Foobar" >> "output.txt"
Wait/Sleep
sleep 600
Environment Variables
You can printenv
or set
Environment variables:
USERDOMAIN - domain USER - The current logged in user. LOGNAME - The name of the current user. HOME - The home directory of the current user. SHELL - The path of the current user’s shell, such as bash or zsh. PATH - A list of directories to be searched when executing commands. When you run a command the system will search those directories in this order and use the first found executable. UID - User ID HOSTNAME: The hostname of the computer at this time. BASH_VERSION: The version of bash being executed, in human-readable form. BASH_VERSINFO: The version of bash, in machine-readable output.
Exiting
exit 0
Math - Incrementing Counters
If a single bracket tells it scope, a double bracket tells it to handle the scope as math:
h (($SomeVar++)) # or i=$((i+1)) # 1 becomes 2
which is different than
($SomeVar)+($SomeVar) # which probably comes out as "1+1"
And
$SomeVar+$SomeVar # which probably comes out as "1+1"
Basic Ideas
# rename files for file in *.jpeg do mv -- "$file" "${file%.jpeg}.jpg" done