I don't know everything, but here's the best takeaways from me thus far
This is my altar to everything that can and will go wrong in bash
- Alias frequent commands in your
.bashrc
. Do not echo in your.bashrc
, it breaks SFTP and other programs. Try/etc/motd
for welcome messages instead. ^_^ if [ -z "$SSH_AGENT_PID" ]; then eval "$(ssh-agent)"; fi; ssh-add $key
to avoid retyping your SSH key's password.- Learn regex at https://regexr.com. Bash uses globbing but some commands use regex. See also: Special Characters
- Learn vim and add
colo murphy
to your.vimrc
. Murphy is the most readable color scheme to me.set (no)number
andset (no)hlsearch
are also useful. man -f $cmd [...]
lists short descriptions of several commands.less +G $file
opens $file at the bottom.- Like
git grep
? Trygrep -r -- blah
outside of git repos. - Nesting quotes is like disarming explosives.
- Press Shift-Ctrl-C/X/V to copy/cut/paste. Press Ctrl-C/D to kill program/EOF. Press Shift-PgUp/Down to page up/down.
- See Filesystem Hierarchy Standard.
- Sort file length by lines (try
wc -c
for characters)find . -type f ! -wholename '*/.*' | xargs -rd '\n' wc -l | sort -n
ssh -t $host tmux a -t $sessionname
attaches to a tmux session over SSH.- Use
time
to test your script's functionality and runtime. - Type Unicode with Character Map.
$'\n'
is newline, and \ escapes spaces. See also: Escape character - Use git for backups. I admit there's a learning curve and you need to commit regularly but it's worth it. Also, never upload git repos with classified data in their history, and blacklist classified files in
.gitignore
. - Use rsync instead of SCP, it's better maintained (Ctrl-F outdated).
- π₯ Use shellcheck to lint your scripts. Like autocorrect it's not always right but it catches mistakes you miss. Usually those mistakes are forgetting to quote variables. Sometimes quoting is the problem too. Don't you just love Bash already?
Abusing grep for dummies:
# Does $2 equal pie?
if echo 'banana masonry pie' | grep -q pie; then echo hi; fi
hi
dolla2=$(echo 'banana masonry pie' | awk '{print $2}')
if [ "$dolla2" = pie ]; then echo hi; fi
# Get flavor of pie
echo food=pie | grep pie | cut -d = -f 2
pie
echo pie=Safeway-flavored | grep ^pie= | cut -d = -f 2
Safeway-flavored
# Safeway-flavored Slurpees, Safeway-flavored vaping, just think of the brand crossover opportunities! $$$
# $file:
# the_only_one is Highlander
# the_only_one is Breakin' 2: Electric Boogaloo
# Use head to only get the first match
the_only_one=$(grep the_only_one "$file" | head -n 1)
Arm
ageddon:
var=yar
# Typo does not exist
empty_var=$vat
# 1: This expands to rm -rf / which removes everything on your computer
# 2: Trailing / is not even required in this case
# 3: Bask in the glorious flames of your error and pray to the backup gods
rm -ri "empty_var"/
# Fail if $empty_var is empty
rm -ri "${empty_var:?}"/
cd blah
-bash: cd: blah: No such file or directory
# Remove * in . which is definitely not blah
rm -ri *
if ! cd blah; then exit 1; fi
# or remove all files found in blah, N/A in this case
rm -ri $(find blah -mindepth 1 -maxdepth 1 -type f)
touch γγγγfooγ§γγ
# Remove filename you can't type
# Ask you if you want to delete every file in the directory for each
rm * -i
# *foo* will only match γγγγfooγ§γγ
rm *foo* -i
pwd
/
# Remove incriminating evidence files except the 3 newest ones
# If evidence dir exists but is empty, find has no output
# ls lists the working directory if given no input
# Is that a pipe to rm? Better get a life insurance policy before the boss garrotes you
find /tmp_files/nope/go/back/seriously/evidence -type f | xargs -d '\n' ls -t | tail -n +4 | xargs -d '\n' rm -i
man xargs
/^ *-r
# -r, --no-run-if-empty
# If the standard input does not contain any nonblanks, do not run the command
# Normally, the command is run once even if there is no input
# This option is a GNU extension
# Competent drug cartel sysadmins use the -r flag with xargs ls
find /tmp_files/nope/go/back/seriously/evidence -type f | xargs -rd '\n' ls -t | tail -n +4 | xargs -d '\n' rm -i
# You might think that cp is a cute innocent command, but let me tell you, you are wrong
# `cp yolo precious_file` is just as dangerous as any rm ever was
Code Injection:
# This command is harmless, not chemically reactive, and probably OSHA approved
yolo='"$(touch /tmp/yolo)"'
# printf is a Bash shell builtin
# printf --help | less
sudo runuser -l mc -s /bin/bash -c "$(printf 'ls %q' "$yolo")"
ls: cannot access '"$(touch /tmp/yolo)"': No such file or directory
sudo runuser -l mc -s /bin/bash -c "ls $yolo"
ls: cannot access '': No such file or directory
# yolo could be ransomware instead of touch
ls /tmp/yolo
/tmp/yolo
yolo=--yolo
ls -- "$yolo"
ls: cannot access '--yolo': No such file or directory
ls "$yolo"
ls: unrecognized option '--yolo'
Try 'ls --help' for more information.
cut:
echo this | cut -d ' ' -f 2
this
# Without -s the first string is printed if there isn't a 2nd
echo this | cut -d ' ' -f 2 -s
echo 'this is sparta' | cut -d ' ' -f 2 -s
is
man cut | cut -d $'\n' -f 7
cut OPTION... [FILE]...
# cut -d $'\n' doesn't work on CentOS 7
man cut | head -n 7 | tail -n 1
# '' before the leading space is field 1
echo ' 1 2' | cut -d ' ' -f 2
1
echo ' 1 2' | awk '{print $2}'
2
Danger Noodle π:
yolo=yolo
python3 -c "print(\"$yolo\")"
yolo
yolo='"); print("^_^ code injection-senpai!"); print("yolo'
python3 -c "print(\"$yolo\")"
^_^ code injection-senpai!
yolo
python3 -c 'import sys; print(sys.argv[1])' "$yolo"
"); print("^_^ code injection-senpai!"); print("yolo
# https://docs.python.org/3/reference/compound_stmts.html
# Fun fact: If you do this, the Federal Bureau of Code Quality will put you on a watchlist for unamerican activities
python3 -c 'for _ in range(3):'$'\n'' print(1)'$'\n'' for _ in range(3):'$'\n'' print(10)'$'\n''print(100)'
1
10
10
10
1
10
10
10
1
10
10
10
100
Exception Handling:
# Exit if a command fails
set -e
# || true makes $optional empty instead of failing
optional=$(ls optional || true)
# If a command fails rm yolo before exiting
trap 'rm yolo' ERR
# Go back to default behavior
trap - ERR
set +e
# ls on exit
trap ls EXIT
# ls and kill session processes of $$ (script PID) on exit
trap 'ls; pkill -s $$' EXIT
# `pkill -s $$` with systemd `KillMode=mixed`
# Default `KillMode=control-group` kills all processes, if ls takes too long systemd will see it and kill it
# `pkill -s $$` kills remaining processes after ls finishes
set -e
yolo() { false; echo nope; }
yolo
# Snake? Snake! Snaaaake!!!!
set -e
yolo() { false; echo nope; }
if yolo; then echo 'oh yeah'; fi
nope
oh yeah
# set -e can't save you now
yolo() { if ! false; then return 1; fi; echo nope; }
if yolo; then echo 'oh yeah'; fi
:flags:
π (flag emojis garble MAC addresses π’):
# getopt lets you pass flags to a script
# `script.sh -ab bean coffee money`
# `script.sh --awesome --burrito bean coffee money`
# `script.sh -b velveeta crumbs sadness`
syntax='Usage: script.sh [OPTION]... ARG1 ARG2'
args=$(getopt -l awesome,burrito:,help -o ab:h -- "$@")
eval set -- "$args"
while [ "$1" != -- ]; do
case $1 in
--awesome|-a)
awesome=true
shift
;;
--burrito|-b)
burrito=$2
# Type check with regex
if [[ ! "$burrito" =~ ^[A-Za-z0-9]+$ ]]; then
>&2 echo 'BURRITO must be alphanumeric'
exit 1
fi
shift 2
;;
--help|-h)
echo "$syntax"
echo 'Insert description here.'
echo
echo 'Options:'
echo '-a, --awesome if set you are awesome'
echo '-b, --burrito=BURRITO type of burrito'
echo
echo 'Insert details here.'
exit
;;
esac
done
shift
if [ "$#" -lt 2 ]; then
>&2 echo 'Not enough arguments'
>&2 echo "$syntax"
exit 1
elif [ "$#" -gt 2 ]; then
>&2 echo 'Too much arguments'
>&2 echo "$syntax"
exit 1
fi
arg1=$1
arg2=$2
Internal Field Separator:
str=a,b,c
for x in $str; do echo "$x"; done
IFS=,
for x in $str; do echo "$x"; done
# Restore default behavior
unset IFS
# IFS may break trap statement
# Stuff like this is why changing the IFS is my last resort
trap 'unset IFS; do something' ERR
psleaks:
# Open 2 terminals and split screen
# Other users can see password in htop too
yes my password is 1234
htop -u $USER
# Do other users have permission to read your password?
# Jason Chang from Prey made a similar mistake
echo password > file
ls -l file
zip:
cd "$dir"
# zip restores path of file given to it, not just the file itself
# The first argument to tar and zip should be $archive_name, if it's a file you want to archive you'll overwrite it instead
# https://xkcd.com/1168/
zip $archive_name $file [...]
# Make sure a ZIP file really is a ZIP file
unzip -t $archive