The shebang (#!) must be the first line of every script. It tells Linux which interpreter to execute the script with. #!/bin/bash means "run this using the bash interpreter located at /bin/bash".
By default, files don't have execute permission. Use chmod +x to add execute permission. Then you can run ./script.sh directly instead of bash script.sh. Without execute permission, Linux treats it as a regular file, not a program.
echo writes text to stdout (your terminal). It's how scripts communicate with users. By default echo adds a newline at the end. Use -n flag to suppress it. echo can display variables, command output, and any text.
Comments start with # and are completely ignored by bash. They're for humans to read. Good comments explain WHY you're doing something, not WHAT (the code shows that). Always add a header block explaining the script's purpose, author, and usage.
Variables store values for later use. Syntax: NAME=value with NO SPACES around =. Variable names are case-sensitive (NAME and name are different). By convention, system variables use UPPERCASE, local variables use lowercase.
Use $ to read a variable. Without $, bash treats the name as literal text. Always quote variables to prevent word splitting—spaces in values break your commands if unquoted. This is one of the most common bugs in shell scripts.
Command substitution $(command) runs a command and saves its output to a variable. This is one of bash's most powerful features. Use $() syntax (modern and nestable), not backticks (deprecated and harder to read).
Bash automatically creates special variables: $0=script name, $1/$2/$3=arguments, $#=argument count, $?=exit code of last command, $$=process ID. These enable scripts to be flexible and handle errors.
Use $(( )) to perform integer arithmetic. Inside, you don't need $ before variables. Supports +, -, *, /, % (modulo), ++, --. Bash normally treats everything as text, so $(( )) tells bash "treat these as numbers".
Use -eq, -ne, -lt, -le, -gt, -ge inside [ ] for number comparisons. Don't use = or < (those are for text). [ ] is the test command—it evaluates conditions and returns true/false.
Test files with [ -f file ] (is file?), [ -d dir ] (is directory?), [ -e path ] (exists?), [ -r ] (readable?), [ -w ] (writable?), [ -x ] (executable?), [ -s ] (non-empty?). Essential for safe, production-grade scripts.
Combine conditions: && (AND—both true), || (OR—at least one true), ! (NOT—negate). && runs right side only if left succeeds. || runs right side only if left fails. This enables powerful conditional logic and error handling.
if tests a condition. If true, execute the then block. Use elif for multiple branches, else for the fallback. Always end with fi. This is how you make scripts make decisions.
for loops iterate over lists, ranges, or files. Common syntaxes: for i in {1..10}, for file in *.log, for ((i=0; i<10; i++)). Always end with done. This is how you automate repetitive tasks.
while loops run as long as a condition is true. Useful for reading files, waiting for conditions, retries. Always ensure the condition eventually becomes false to avoid infinite loops. Common pattern: read input, process it, check condition.
case evaluates a value against patterns. Much cleaner than multiple if/elif when checking one variable. Each pattern ends with ;;. Use * as default fallback. Supports wildcards for pattern matching.
Functions group code for reuse and organization. Define with function_name() { }. Call by name. Access arguments via $1, $2, etc. Return values with return (0-255) or echo. This is essential for maintainable scripts.
Arrays store lists of values. Create with (space-separated). Access with ${array[index]}. Use ${#array[@]} for length, ${array[@]} for all elements, ${array[@]:start:length} for slices. Useful for batch operations.
sed (stream editor) and awk are powerful text processing tools. sed modifies text (find/replace, delete lines). awk processes text in columns/fields. Both use regex. Essential for log analysis, data transformation, and text manipulation.
set -e exits script if any command fails. set -u errors on undefined variables. set -o pipefail exits if any command in a pipe fails. These prevent scripts from continuing after errors—essential for production scripts.
Production scripts require care: validate inputs, log operations, handle errors, use clear variable names, quote everything, test before production, document thoroughly. These practices separate scripts that work from scripts you can trust with critical operations.