Skip to content

Instantly share code, notes, and snippets.

@TapeWerm
Last active June 10, 2024 03:08
Show Gist options
  • Save TapeWerm/147ae50d81aa64c6701c0b8add8c476a to your computer and use it in GitHub Desktop.
Save TapeWerm/147ae50d81aa64c6701c0b8add8c476a to your computer and use it in GitHub Desktop.
Bash Advice.md - I don't know everything, but here's the best takeaways from me thus far

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

Tips

  • 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 and set (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? Try grep -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?

Lessons

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)

Armageddon:

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 all files in . which is definitely not blah
rm -ri $(ls -A)
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 counted 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 Mandatory arguments to long options are mandatory for short options too.
		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 -r $archive_name $file [...]
# Make sure a ZIP file really is a ZIP file
unzip -t $archive

Suggested links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment