Shell Mastery — Alternate Commands
File Creation (without touch)
touch (the obvious way)
touch file.txt
Redirect (empty file)
> file.txt
echo
echo "hello" > file.txt
printf (more portable than echo)
printf '%s\n' "hello" > file.txt
Heredoc (multi-line config files)
cat << 'EOF' > file.conf
server {
listen 80;
root /var/www;
}
EOF
install (set permissions at creation)
install -m 0644 /dev/null file.txt
install -m 0600 /dev/null secret.key
install -d -m 0755 new_directory
dd (create file of specific size)
dd if=/dev/zero of=file.bin bs=1M count=10 2>/dev/null
truncate
truncate -s 0 file.txt # empty existing file
truncate -s 1M file.bin # create 1MB sparse file
tee (create + display)
echo "content" | tee file.txt
echo "more" | tee -a file.txt # append
cp from /dev/null
cp /dev/null file.txt
Reading Files (without cat)
less / more
less file.txt
more file.txt
head / tail
head -20 file.txt
tail -20 file.txt
tail -f log.txt # follow
awk (print entire file)
awk '{print}' file.txt
awk '1' file.txt # shortest awk to print all
sed
sed '' file.txt
sed -n '1,20p' file.txt # lines 1-20
while read loop
while IFS= read -r line; do echo "$line"; done < file.txt
Redirect to stdin
< file.txt grep "pattern"
mapfile/readarray (into array)
mapfile -t lines < file.txt
echo "${lines[0]}" # first line
echo "${#lines[@]}" # total lines
Conditionals — [[ ]] vs [ ] vs test
[[ ]] is the bash/zsh enhanced test. Always prefer it over [ ] in scripts.
Why [[ ]] is better
# [ ] is a command — needs careful quoting
[ "$var" = "value" ] # works
[ $var = "value" ] # BREAKS if var is empty or has spaces
# [[ ]] is a keyword — handles quoting internally
[[ $var = "value" ]] # always works, even if var is empty
[[ $var == "value" ]] # == also works in [[ ]]
Pattern matching (only in [[ ]])
[[ $file == *.txt ]] && echo "text file"
[[ $host == web-* ]] && echo "web server"
[[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "valid IP"
Regex matching (only in [[ ]])
if [[ $line =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2}) ]]; then
year="${BASH_REMATCH[1]}"
month="${BASH_REMATCH[2]}"
day="${BASH_REMATCH[3]}"
echo "Date: $year-$month-$day"
fi
Logical operators
# [ ] uses -a and -o (fragile)
[ "$a" = "1" -a "$b" = "2" ]
# [[ ]] uses && and || (readable)
[[ $a == "1" && $b == "2" ]]
[[ $a == "1" || $b == "2" ]]
String tests
[[ -z $var ]] # empty
[[ -n $var ]] # not empty
[[ $a == $b ]] # equal
[[ $a != $b ]] # not equal
[[ $a < $b ]] # alphabetical (no quoting needed in [[ ]])
File tests
[[ -f file.txt ]] # regular file exists
[[ -d /path ]] # directory exists
[[ -e anything ]] # anything exists
[[ -r file.txt ]] # readable
[[ -w file.txt ]] # writable
[[ -x script.sh ]] # executable
[[ -s file.txt ]] # exists and non-empty
[[ -L link ]] # is symlink
[[ file1 -nt file2 ]] # file1 newer than file2
[[ file1 -ot file2 ]] # file1 older than file2
Arithmetic (use instead)
(( count > 10 )) && echo "too many"
(( count++ ))
(( total = a + b * c ))
(( RANDOM % 6 + 1 )) # dice roll
Competition patterns — one-liner conditionals
# Check if command exists
[[ $(command -v jq) ]] && echo "jq available" || echo "jq missing"
# Check if file is non-empty before processing
[[ -s data.csv ]] && awk -F, '{print $1}' data.csv || echo "empty file"
# Check if variable looks like an IP
[[ $host =~ ^[0-9]+\. ]] && echo "IP" || echo "hostname"
# Check if running as root
[[ $EUID -eq 0 ]] && echo "root" || echo "not root"
# Check if in a git repo
[[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1 && echo "git repo"
Directory Creation (without mkdir)
mkdir (obvious)
mkdir -p /path/to/deep/dir
install -d
install -d -m 0755 /path/to/dir
Using a command that creates dirs as side effect
# rsync creates destination dirs
rsync -a /dev/null /path/to/dir/
# git init creates .git + working dir
git init /new/repo
Process Execution Alternatives
Run a command
ls # direct
command ls # bypass aliases/functions
builtin cd # use builtin, not function
env ls # fresh environment
exec ls # replace shell with command
Background & parallel
cmd & # background
cmd1 & cmd2 & wait # parallel with wait
nohup cmd & # survive logout
setsid cmd # new session
Subshell vs current shell
(cd /tmp && ls) # subshell — doesn't change your pwd
{ cd /tmp && ls; } # current shell — DOES change your pwd
Permissions (without chmod)
chmod (obvious)
chmod 755 script.sh
chmod u+x script.sh
install (set perms at copy time)
install -m 0755 script.sh /usr/local/bin/
cp with --preserve
cp --preserve=mode source dest
umask (set default for new files)
umask 022 # new files: 644, new dirs: 755
umask 077 # new files: 600, new dirs: 700