shift — Processing Arguments Sequentially
The shift command moves positional parameters left, letting you consume arguments one at a time. Useful for custom option parsing and processing variable-length argument lists.
shift moves the positional parameters one step to the left: $2 becomes $1, $3 becomes $2, and so on. $1 is discarded, and $# decreases by 1. It's how you consume arguments one at a time in a loop.
Basic shift
#!/usr/bin/env bash
# Called as: ./script.sh a b c d
echo "Before:"
echo "$# = $#" # 4
echo "$1 = $1" # a
echo "$2 = $2" # b
shift
echo "After shift:"
echo "$# = $#" # 3
echo "$1 = $1" # b (was $2)
echo "$2 = $2" # c (was $3)
shift 2 # shift by 2
echo "After shift 2:"
echo "$# = $#" # 1
echo "$1 = $1" # dProcessing All Arguments with shift
#!/usr/bin/env bash
# Sum all numeric arguments
total=0
while [[ $# -gt 0 ]]; do
total=$(( total + $1 ))
shift
done
echo "Total: $total"
# Usage: ./sum.sh 1 2 3 4 5 → Total: 15Manual Flag Parsing with shift
#!/usr/bin/env bash
verbose=false
output=""
while [[ $# -gt 0 ]]; do
case "$1" in
-v | --verbose)
verbose=true
shift
;;
-o | --output)
if [[ $# -lt 2 ]]; then
echo "ERROR: -o requires an argument" >&2
exit 1
fi
output="$2"
shift 2 # shift past flag AND its value
;;
--output=*)
output="${1#--output=}" # strip --output= prefix
shift
;;
--)
shift
break # everything after -- is a positional arg
;;
-*)
echo "Unknown flag: $1" >&2
exit 1
;;
*)
break # first non-flag argument
;;
esac
done
# Remaining $@ are positional args
echo "Verbose: $verbose"
echo "Output: $output"
echo "Remaining args: $@"shift 2 for options with arguments
When an option takes an argument (like -o file.txt), you need shift 2 — once to discard the flag (-o) and once to discard its value (file.txt). Forgetting to shift by 2 causes the next iteration to try to parse file.txt as a flag.
A script receives args `one two three`. After `shift 2`, what is `$1`?
Write a script install.sh that accepts:
--prefix /pathor-p /path→ installation prefix (default/usr/local)--dry-runor-n→ print what would be done without doing it- One positional argument: the package name
Use shift-based manual parsing. Print a summary: "Installing PACKAGE to PREFIX (dry: true/false)"