If you want to be absolutely certain that your script is being dot-included, escape the dot.
# note the dot is escaped, and has a space to where the invoked script actually exists
\. "$NVM_DIR/nvm.sh"
source script.sh |
exec script.sh |
./path/to/script.sh |
---|---|---|
Invokes the targeted script in the current shell. . and source are usually aliases of each other used interchangably. |
Terminates the current shell and then execute the target script in place. | Invokes the target script in a new child shell. ./ denotes the current directory. |
This writeup by an enterprising stack overflow user is really the best explanation I have found online.
To invoke a script via exec
or .
requires execute
permissions on the file. However, if you source
it you do not require execute
permissions on the file!
To modify the appearance of the prompt message that appears on every line, two actions must be completed.
C:/repo [N/A]|>
^^^^^^^^ <-- custom prompt
- Override the
PROMPT_COMMAND
variable to set a custom prompt function. - Within the custom prompt function, ensure you set
PS1
variable. This is the environment variable containing the prompt message for the current line.
prompt_func() {
PS1="\w \e"
}
PROMPT_COMMAND=prompt_func
To see what is currently defined in your .bashrc
/.bash_profile
use grep 'PS1' $HOME/.bash{rc,_profile}
. There are a few different prompt variables:
Name | Purpose |
---|---|
PS1 |
Default interactive prompt (this is the variable most often customized). |
PS2 |
Continuation interactive prompt (when a long command is broken up with \ at the end of the line) default=> . |
PS3 |
Prompt used by select loop inside a shell script. |
PS4 |
Prompt used when a shell script is executed in debug mode (set -x will turn this on) default =++ . |
PROMPT_COMMAND |
If this variable is set and has non-null value, it will be executed (as a function) just before the PS1 variable output. |
Special prompt variable characters:
Character | Substitution Value |
---|---|
\d |
The date, in "Weekday Month Date" format (e.g., "Tue May 26"). |
\h |
The hostname, up to the first . (e.g. deckard) |
\H |
The hostname. (e.g. deckard.SS64.com) |
\j |
The number of jobs currently managed by the shell. |
\l |
The basename of the shell's terminal device name. |
\s |
The name of the shell, the basename of $0 (the portion following the final slash). |
\t |
The time, in 24-hour HH:MM:SS format. |
\T |
The time, in 12-hour HH:MM:SS format. |
\@ |
The time, in 12-hour am/pm format. |
\u |
The username of the current user. |
\v |
The version of Bash (e.g., 2.00) |
\V |
The release of Bash, version + patchlevel (e.g., 2.00.0) |
\w |
The current working directory. |
\W |
The basename of $PWD. |
\! |
The history number of this command. |
\# |
The command number of this command. |
\$ |
If you are not root, inserts a "$"; if you are root, you get a "#" (root uid = 0) |
\nnn |
The character whose ASCII code is the octal value nnn. |
\n |
A newline. |
\r |
A carriage return. |
\e |
An escape character (typically a color code). |
\a |
A bell character. |
\\ |
A backslash. |
\[ |
Begin a sequence of non-printing characters. (like color escape sequences). This allows bash to calculate word wrapping correctly. |
\] |
End a sequence of non-printing characters. |
It is recommended to use '
instead of "
when exporting PS variables, as it makes the prompt a tiny bit faster to evaluate.
A variable must:
- Start with a letter
[a-zA-Z]
. - Can include a digit [0-9] as long as the function name does not begin with one.
- Can include a
_
, though a variable can be started with an_
.
a=(ls -l)
a=$ENV_SETTING
a="a string"
read a
echo $a
# or
echo "${a}"
A note about assigning variables. Don't have any space on EITHER side of the =
sign. It's much easier to read, but you will have really strange issues.
VARIABLE1="hello there good sir"
echo '$VARIABLE1' # -> ''. Because bash doesn't expand variables when surrounded by single quotes
echo "$VARIABLE1" # -> "hello there good sir". Because bash DOES expand variables when surrounded by double quotes.
foo=
echo ${foo:-"variable if unset"}
echo $foo # -> "variable if unset"
foo=3
echo ${foo:-"variable if unset"}
echo $foo # -> "3"
Operation | Result |
---|---|
${parameter:-word} |
Returns the value of word if parameter is unset or empty. |
${parameter:=word} |
Returns the value of word if parameter is unset or empty. Also assigns the value of word to parameter . |
${parameter:?word} |
Exits the script with an error described in word if parameter is unset or empty. |
Note that the first time a variable is set and used, it is visible outside of the scope where it is defined.
func() {
global_var=42
}
echo "$global_var" # -> empty
func
echo "$global_var" # -> 42
To bind a variable name only to the current scope or code block, preface the declaration with local
.
func() {
local local_var=42
}
echo "$local_var" # -> empty
func
echo "$local_var" # -> empty, local_var remained constrained to function block.
Arguments to the script are accessed through the $N
variable. Note that script arguments are not immediately accessible to function
scopes. One must pass a script argument on to the function to make it available.
# arg_example.sh
echo $1 $2
function helloworld() {
echo $1 $2
}
helloworld $1 $2
helloworld $2 $1
helloworld chicken butt
Called as below:
semick@terra:~/repo/locker/armory/interface/sh-cli$ ./arg_example.sh hello world
Original hello world
function chicken world
function world butt
Check the parameter expansion
section below.
function myfuncname() {
local b=(ls -l)
echo "hello world!"
echo b
}
myfuncname()
function myfunctioname() {
local b=${1:-"hahaha!"}
echo $b
}
myfunctioname "moop"
if ["$x" -eq 5]; then
echo "x equals 5"
else
echo "nope"
fi
if [ bool ]; then
# <expr>
elif [ bool ]; then
# <expr>
else
# <expr>
fi
Separate statements with ;
.
# simple form
if [ -d $STAGING_DIRECTORY ]; then echo "exists!"; else echo "returning false!"; fi
# advanced form
if [[ -d $STAGING_DIRECTORY ]]; then echo "exists!"; else echo "returning false!"; fi
# test form
if test -d $STAGING_DIRECTORY; then echo "exists!"; else echo "returning false!"; fi
If possible, prefer the new test
EG [[ EXPRESSION ]]
syntax. It gives you access to better operators like REGEX!
if [[ "$x" =~ ^-?[0-9]+$ ]]; then
# <expr>
fi
You get access to =~
or regex equality
when you use the [[
instead of a single [
for expressions.
[]
and [[]]
(alternative form (())
) use &&
and ||
for the or
logical operator. An alternative form is to place test
in front of the ;
.
if test $EXPRESSION; then
# <expr>
fi
Combine logical operators in the following way:
Operation | test |
[[ ]] and (( )) |
---|---|---|
AND |
-a |
&& |
OR |
-o |
|| |
NOT |
! |
! |
Expression | is true if |
---|---|
file1 -ef file |
file1 and file2 have the same inode numbers (the two filenames refer to the same file by hard linking). |
file1 -nt file2 |
file1 is newer than file2 . |
file1 -ot file2 |
file1 is older than file2 . |
-b file |
file exists and is a block-special (device) file. |
-c file |
file exists and is a character-special (device) file. |
-d file |
file exists and is a directory. |
-e file |
file exists. |
-f file |
file exists and is a regular file. |
-g file |
file exists and is a set-group-ID. |
-G file |
file exists and is owned by the effective group ID. |
-k file |
file exists and has its "sticky bit" set. |
-L file |
file exists and is a symbolic link. |
-O file |
file exists and is owned by the effective user ID. |
-p file |
file exists and is a named pipe. |
-r file |
file exists and is readable (has readable permission for the effective user). |
-s file |
file exists and has a length greater than 0. |
-S file |
file exists and is a network socket. |
-t fd |
|
-u file |
file exists and is setuid. |
-w file |
file exists and is writable (has write permission for the effective user). |
-x file |
`file exists and is executable (has execute/search permission for the effective user). |
Usage | Meaning |
---|---|
effective group ID |
The group of user invoking the current session. |
effective user ID |
The user invoking the current session. |
file descriptor |
Is a process-unique identifier (handle) for a file or other i/o resource, such as a pipe or a network socket. |
setuid or setguid |
set user identity or set group identity are access flags that allow a user to invoke an executable with the file-system permissions of that executable and not the current user! |
Expression | is true if |
---|---|
string |
string is not null |
-n string |
The length of string is greater than 0. |
-z string |
The length of string is zero. |
string1 = string2 string1 == string2 |
string1 and string2 are equal. Double = form preferred. |
string != string2 |
string1 and string2 are not equal. |
string1 > string2 |
string1 sorts after string2 |
string1 < string2 |
string1 sorts before string2 |
Normally to check if a string is in another string, you'll just use the regex operator.
if [[ $SEARCHSTRING =~ .*"$BIGSTRING".* ]]; then
echo "It's there!"
fi
To compare values as strings, use the following expressions.
Expression | is true if |
---|---|
integer1 -eq integer2 |
integer1 is equal to integer2 . |
integer1 -ne integer2 |
integer1 is not equal to integer2 . |
integer1 -le integer2 |
integer1 is less than or equal to integer2 . |
integer1 -lt integer2 |
integer1 is less than integer2 . |
integer1 -ge integer2 |
integer1 is greater than or equal to integer2 . |
integer1 -gt integer2 |
integer1 is greater than integer2 . |
Surround purely integer expressions in (())
.
INT=5
if ((INT ==0)); then
echo "INT is zero."
fi
echo -n "enter an integer"
read myinteger
echo $myinteger
read -p "Enter a username > " user_name
echo $user_name
count=1
while [[ "$count" -le 5 ]]; do
echo $count
count=$((count + 1))
done
while true ; do
read x
if [[ $x == "a"]]; then
echo "$x == 10"
continue
else
break
fi
done
count=1
until [[ "$count" -gt 5 ]]; do
echo "$count"
count=$((count + 1))
done
So you can read from a file by redirecting input to the loop using the <
operator trailing your loop-end done
.
while read distro version release; do
echo $distro
echo $version
echo $release
done < distros.txt
But another good way is to pipe in the input. That also follows very well.
sort -k 1,1 -l 2n distros.txt | while read distro version release; do
echo $distro
echo $version
echo $release
done
Use exit 1
to exit with a general failure code.
Exit Code Number | Meaning |
---|---|
1 |
Catchall for general errors |
2 |
Misuse of shell builtins (according to Bash documentation) |
126 |
Command invoked cannot execute |
127 |
"command not found" |
128 |
Invalid argument to exit. (this is returned when > 255 is attempted to be returned) |
128+n |
Fatal error signal "n" |
130 |
Script terminated by Control-C |
> 255 |
Exit status out of range |
case "$REPLY" in
[[:alpha:]]) echo "is a single alphabetic character." ;;
[ABC][0-9]) echo "Is A,B, or C followed by a digit." ;;
???) echo "Is 3 characters long." ;;
*.txt|*.json echo "Ends in *.txt or *.json" ;;
*) echo "Something else"
esac
Other [[]]
operations.
Operation | matches if character |
---|---|
[[:upper:]] |
is upper case. |
[[:lower:]] |
is lower case. |
[[:alpha:]] |
is alphabetic. |
[[:digit:]] |
is a digit. |
[[:graph:]] |
is a visible character. |
[[:punct:]] |
is a punctuation symbol. |
[[:space:]] |
is a whitespace character. |
[[:xdigit:]] |
is a hexadecimal digit. |
If you want to evaluation to continue past a match, simply add a trailing &
to a cases terminating ;;
.
case "$REPLY" in
[[:alpha:]]) echo "is a single alphabetic character." ;;&
[ABC][0-9]) echo "Is A,B, or C followed by a digit." ;;&
???) echo "Is 3 characters long." ;;&
*.txt|*.json echo "Ends in *.txt or *.json" ;;&
*) echo "Something else"
esac
# $REPLY is "333"
# will print "is 3 characters long" and "Something else" because the case cascades though due to the `&`.
Single dimension only.
# created automatically when accessed
a[1]=foo
echo ${a[1]}
An array can also be created bare by:
# -a is what makes `a` an array variable
declare -a a
You can assign in two ways.
name[subscript]=value
name=(index0 index1 index2 index3 ...)
days=(Sun Mon Tue Wed Thu Fri Sat)
days=([0]=Sun [1]=Mon [2]=Tue [3]Wed [4]Thu [5]Fri [6]Sat)
Ok, so how can we actually use the above? How do we iterate?
#!/usr/bin/bash
usage() {
echo "usage: ${0##*/} directory" >&2
}
if [[ ! -d "$1" ]]; then
usage
exit 1
fi
for i in {0..23}; do hours[i]=0; done
for i in $(stat -c %y "$1" /* | cut -c 12-13); do
j="{i#0}"
((++hours[j]))
((++count))
done
echo -e "Hours\tFiles\tHour\tFiles"
echo -e "-----\t-----\t----\t-----"
for i in {0..11}; do
j=$((i + 12))
printf "%02d\t%d\t02d\t%d\n" \
"$i" \
"${hours[i]}" \
"$j" \
"${hours[j]}"
done
printf "\n Total files = %d\n" $count
You can declare an array which can by accessed by string value.
declare -A fruits
fruits[india]="Banana"
fruits[california]="Orange"
fruits[washington]="Apple"
echo ${fruits[washington]}
if [ $(ls | wc -l) == 0 ]; then echo 1; else echo 0; fi
This can be terser though, if you don't need the else
side.
[ <boolean expression> ] && echo "successful if"