Skip to content
C.W.K.
Stream
Lesson 11 of 12 · published

Heredocs and Script Arguments

~10 min · heredoc, args, getopts

Level 0Window Tourist
0 XP0/95 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Heredocs — embedded multi-line strings

cat <<EOF
Hello, $USER
Today is $(date +%Y-%m-%d)
EOF

Anything between the opening token (here EOF) and the closing one becomes stdin to the command. Variables and command substitutions expand by default.

Disable expansion: quote the delimiter

cat <<'EOF'
$USER stays literal
$(date) stays literal
EOF

Quoting any character of the opening delimiter (typically the whole 'EOF') disables variable expansion. Use this for embedded config, scripts, or templates that contain shell-like syntax you don't want expanded.

Indent a heredoc with <<-

cat <<-EOF
	hello
	world
	EOF

<<- strips leading tabs (not spaces) from each line — handy for keeping heredocs indented inside if blocks.

Script arguments

  • $0 — script name.
  • $1 $2 ... — positional args.
  • $# — number of args.
  • $@ — all args. Always quote: "$@".
  • $* — args joined by IFS — usually wrong.
  • shift — drop $1, renumber.

Parsing flags with getopts

verbose=0
while getopts ':vo:' opt; do
  case "$opt" in
    v) verbose=1 ;;
    o) output="$OPTARG" ;;
    \?) echo "bad flag: -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

POSIX getopts handles short flags only. For long flags, either parse them by hand or use bash's getopt variant or switch to Python's argparse. Most scripts get away with short-only.

Preserve argument and expansion boundaries

Forward caller arguments with "$@", not an unquoted aggregate. Choose quoted or unquoted heredoc delimiters deliberately: an unquoted delimiter expands substitutions while the shell constructs stdin; a quoted delimiter preserves the body literally.

The boundary: <<- never strips spaces

The dash form can indent a heredoc with tab characters; it does not remove spaces. Make invisible indentation deliberate.

A heredoc is not a safe template engine

An unquoted delimiter expands variables, commands, and arithmetic while the shell constructs stdin. Quote the delimiter for literal data and use a real template or structured serializer when substitution rules become complex.

Code

Heredoc + getopts in one script·bash
#!/usr/bin/env bash
set -euo pipefail
name=World
while getopts ':n:' opt; do
  case "$opt" in
    n) name=$OPTARG ;;
    *) echo "usage: $0 [-n NAME]" >&2; exit 2 ;;
  esac
done
cat <<EOF
Hello, $name!
from $0
EOF

External links

Exercise

Write greet.sh that takes -n NAME (default World) and prints a two-line greeting via heredoc. Test with ./greet.sh, ./greet.sh -n Alice, ./greet.sh -x (should error).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.