|

Linux CLI 54 🐧 shell script variables

my_variable="Hello, World!"
echo $my_variable
unset my_variable
echo $my_variable

#!/bin/bash

# Declare and initialize variables
greeting="Hello, World!"
name=$1
age=30

# Accessing variables
echo $greeting
echo "Name: $name"
echo "Age: $age"

# Using special variables
echo "Script name: $0"
echo "Number of arguments: $#"
echo "All arguments as a single word: $*"
echo "All arguments as separate words: $@"
echo "Process ID: $$"
echo "Exit status of last command: $?"

Variables are the foundation of any shell script. They let you store values, pass data around, and make your scripts flexible instead of hardcoded. Shell scripts also come with a set of special variables that the shell fills in automatically — the script name, arguments, process ID, and exit status.

Key point: In shell scripting, variables are untyped — they hold strings by default, and the shell decides how to interpret them based on context. There are no spaces around the = when assigning, and you use $ to read a variable’s value.


a – variables

Variables are used to store and manipulate data. In shell scripting, a variable is just a name that points to a value.

Declaring variables:

Assign a value using =:

my_variable="Hello, World!"

Rules:

RuleExample
No spaces around =VAR="value" ✅ — VAR = "value"
Names can have letters, digits, underscoresmy_var, var2, _tmp
Names can’t start with a digit2var ❌ — var2
Names are case-sensitiveVar and var are different
Values can be quoted or unquotedVAR=hello or VAR="hello world"

Accessing variables:

Use $ before the name:

echo $my_variable
echo ${my_variable}

The ${...} form is safer — it avoids ambiguity when the variable name runs into adjacent text:

$ name="Kronos"
$ echo "Hello, $name!"
Hello, Kronos!

$ echo "Hello, ${name}!"
Hello, Kronos!

# Without braces, this fails:
$ echo "$name_file"          # looks for $name_file — empty!
$ echo "${name}_file"        # correct: Kronos_file

Quoting matters:

$ greeting="Hello, World!"
$ echo $greeting
Hello, World!

# Same result here because there's no glob or extra whitespace
$ echo "$greeting"
Hello, World!

# But quoting protects against word splitting and globbing:
$ files="*.txt"
$ echo $files           # expands the glob!
[ file1.txt file2.txt ]
$ echo "$files"         # literal: *.txt
[ *.txt ]

Best practice: Always quote your variables — "$VAR" — unless you specifically want word splitting or globbing.

Unsetting variables:

$ my_variable="Hello, World!"
$ echo $my_variable
[ Hello, World! ]

$ unset my_variable
$ echo $my_variable
[ ]

Using variables in scripts and on the command line:

# On the command line
$ name="Kronos"
$ echo "Hello, $name"
[ Hello, Kronos ]

# In a script
$ cat > greet.sh << 'EOF'
#!/bin/bash
name="Kronos"
echo "Hello, $name"
EOF
$ chmod +x greet.sh
$ ./greet.sh
[ Hello, Kronos ]

Note: Variables set in a script are local to that script — they don’t persist after the script exits. Variables set on the command line persist in that shell session only.

Assigning from commands (command substitution):

# Use $(...) to capture command output
$ TODAY=$(date +%F)
$ echo "Today is $TODAY"
[ Today is 2024-01-15 ]

$ FILES=$(ls *.txt)
$ echo "Text files: $FILES"
[ Text files: file1.txt file2.txt ]

$ HOSTNAME_SHORT=$(hostname -s)
$ echo "Host: $HOSTNAME_SHORT"
[ Host: olympos ]

Assigning from user input:

$ read -p "What is your name? " name
What is your name? Kronos
$ echo "Hello, $name!"
[ Hello, Kronos! ]

Arithmetic:

$ a=5
$ b=3
$ sum=$((a + b))
$ echo $sum
[ 8 ]

$ ((a++))
$ echo $a
[ 6 ]

Variable scope:

ScopeHow
Local to shell sessionVAR=value
Exported to child processesexport VAR=value
Local to a functionlocal VAR=value
Read-onlyreadonly VAR=value
# Export to child processes
$ export EDITOR="nano"
$ bash -c 'echo $EDITOR'
[ nano ]

# Local to a function
$ myfunc() {
    local tmp="secret"
    echo "$tmp"
  }
$ myfunc
[ secret ]
$ echo "$tmp"
[ ]

Common built-in variables:

VariableMeaning
HOMEHome directory
USERCurrent user
PATHCommand search path
PWDCurrent directory
SHELLCurrent shell
LANGLocale
EDITORDefault editor

Examples of variable assignment and use:

# Simple string
$ greeting="Hello, World!"
$ echo "$greeting"
[ Hello, World! ]

# Number
$ count=42
$ echo "Count: $count"
[ Count: 42 ]

# From a command
$ uptime_info=$(uptime -p)
$ echo "$uptime_info"
[ up 3 days, 4 hours, 22 minutes ]

# From an argument
$ echo "First arg: $1"
[ First arg: hello ]

# Arithmetic
$ x=10
$ y=20
$ echo "Sum: $((x + y))"
[ Sum: 30 ]

# Default value
$ echo "${UNSET:-default}"
[ default ]

# Length of a value
$ name="Kronos"
$ echo "Length: ${#name}"
[ Length: 6 ]

# Substring
$ echo "${name:0:3}"
[ Kro ]

# Uppercase
$ echo "${name^^}"
[ KRONOS ]

# Lowercase
$ echo "${name,,}"
[ kronos ]

b – special variables

Shell scripts have several special variables that are automatically set by the shell. You don’t assign them — they’re provided for you.

VariableMeaning
$0The name of the script
$1$9Positional parameters passed to the script
${10}, ${11}, …Positional parameters past 9 (need braces)
$#Number of positional parameters
$*All positional parameters as a single word
$@All positional parameters as separate words
$$Process ID of the current script
$?Exit status of the last command executed
$!PID of the last background command
$-Current shell options/flags

Examples:

$ cat > args.sh << 'EOF'
#!/bin/bash

echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Number of arguments: $#"
echo "All args (single word): $*"
echo "All args (separate words): $@"
echo "Process ID: $$"
EOF

$ chmod +x args.sh
$ ./args.sh hello world foo
[ Script name: ./args.sh ]
[ First argument: hello ]
[ Second argument: world ]
[ Number of arguments: 3 ]
[ All args (single word): hello world foo ]
[ All args (separate words): hello world foo ]
[ Process ID: 12345 ]

$* vs $@ — the crucial difference:

At first glance they look the same. But when quoted, they behave differently:

#!/bin/bash

echo "With \$*:"
for arg in "$*"; do
    echo "  [ $arg ]"
done

echo "With \$@:"
for arg in "$@"; do
    echo "  [ $arg ]"
done
$ ./test.sh one two three
With $*:
  [ one two three ]
With $@:
  [ one ]
  [ two ]
  [ three ]
FormQuotedResult
$*"$*"All args as one string
$@"$@"Each arg as a separate string
$*unquotedWord-split
$@unquotedWord-split

Rule of thumb: Use "$@" almost always — it preserves argument boundaries and handles spaces correctly.

$? — exit status:

$ ls /etc/passwd
[ /etc/passwd ]
$ echo $?
[ 0 ]

$ ls /nonexistent
[ ls: cannot access '/nonexistent': No such file or directory ]
$ echo $?
[ 2 ]
Exit codeMeaning
0Success
1General error
2Misuse of shell builtin
126Command found but not executable
127Command not found
128+NKilled by signal N
130Interrupted by Ctrl+C

Using $? in scripts:

#!/bin/bash

if [ $? -eq 0 ]; then
    echo "Last command succeeded"
else
    echo "Last command failed"
fi

# Better: check the command directly
if ls /etc/passwd > /dev/null 2>&1; then
    echo "File exists"
else
    echo "File not found"
fi

$$ — process ID:

#!/bin/bash
echo "This script is running as PID $$"

# Useful for unique temp files
TMPFILE="/tmp/myscript-$$.tmp"
echo "Using temp file: $TMPFILE"

$! — last background PID:

$ sleep 100 &
[ 1 ] 12345
$ echo $!
[ 12345 ]
$ kill $!

$0 — script name:

#!/bin/bash
echo "You ran: $0"

# Common idiom: use $0 in usage messages
if [ -z "$1" ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

A complete example — a script that uses all the special variables:

#!/bin/bash

echo "=== Script Info ==="
echo "Script name: $0"
echo "Process ID: $$"
echo "Number of args: $#"
echo

echo "=== Arguments ==="
echo "All args (as one word): $*"
echo "All args (separate): $@"
echo

echo "=== Individual Arguments ==="
for i in "$@"; do
    echo "  [ $i ]"
done
echo

echo "=== Exit Status ==="
ls /etc/passwd > /dev/null
echo "ls exit status: $?"

ls /nonexistent > /dev/null 2>&1
echo "ls (bad) exit status: $?"
$ ./info.sh alpha beta gamma
=== Script Info ===
[ Script name: ./info.sh ]
[ Process ID: 12345 ]
[ Number of args: 3 ]

=== Arguments ===
[ All args (as one word): alpha beta gamma ]
[ All args (separate): alpha beta gamma ]

=== Individual Arguments ===
  [ alpha ]
  [ beta ]
  [ gamma ]

=== Exit Status ===
[ ls exit status: 0 ]
[ ls (bad) exit status: 2 ]

Complete Example Session

# ============================================
# PART 1: BASIC VARIABLES ON THE COMMAND LINE
# ============================================

$ my_variable="Hello, World!"
$ echo $my_variable
[ Hello, World! ]

$ unset my_variable
$ echo $my_variable
[ ]

# ============================================
# PART 2: A SCRIPT WITH VARIABLES
# ============================================

$ cat > vars.sh << 'EOF'
#!/bin/bash

# Declare and initialize variables
greeting="Hello, World!"
name=$1
age=30

# Accessing variables
echo $greeting
echo "Name: $name"
echo "Age: $age"

# Using special variables
echo "Script name: $0"
echo "Number of arguments: $#"
echo "All arguments as a single word: $*"
echo "All arguments as separate words: $@"
echo "Process ID: $$"
echo "Exit status of last command: $?"
EOF

$ chmod +x vars.sh
$ ./vars.sh Alice
[ Hello, World! ]
[ Name: Alice ]
[ Age: 30 ]
[ Script name: ./vars.sh ]
[ Number of arguments: 1 ]
[ All arguments as a single word: Alice ]
[ All arguments as separate words: Alice ]
[ Process ID: 12345 ]
[ Exit status of last command: 0 ]

# ============================================
# PART 3: QUOTING MATTERS
# ============================================

$ name="Kronos"
$ echo "Hello, $name!"
[ Hello, Kronos! ]

$ echo "Hello, ${name}!"
[ Hello, Kronos! ]

# Braces needed when adjacent to other characters
$ echo "${name}_file"
[ Kronos_file ]

$ echo "$name_file"
[ ]

# ============================================
# PART 4: COMMAND SUBSTITUTION
# ============================================

$ TODAY=$(date +%F)
$ echo "Today is $TODAY"
[ Today is 2024-01-15 ]

$ UPTIME=$(uptime -p)
$ echo "$UPTIME"
[ up 3 days, 4 hours, 22 minutes ]

# ============================================
# PART 5: USER INPUT
# ============================================

$ read -p "Your name: " username
Your name: Alice
$ echo "Hello, $username"
[ Hello, Alice ]

# ============================================
# PART 6: ARITHMETIC
# ============================================

$ a=5
$ b=3
$ echo $((a + b))
[ 8 ]
$ echo $((a * b))
[ 15 ]
$ echo $((a - b))
[ 2 ]
$ echo $((a / b))
[ 1 ]

# ============================================
# PART 7: SPECIAL VARIABLES
# ============================================

$ cat > special.sh << 'EOF'
#!/bin/bash
echo "Script: $0"
echo "First arg: $1"
echo "Second arg: $2"
echo "Count: $#"
echo "PID: $$"
echo "Last status: $?"
EOF

$ chmod +x special.sh
$ ./special.sh one two
[ Script: ./special.sh ]
[ First arg: one ]
[ Second arg: two ]
[ Count: 2 ]
[ PID: 12345 ]
[ Last status: 0 ]

# ============================================
# PART 8: $* VS $@
# ============================================

$ cat > star.sh << 'EOF'
#!/bin/bash
echo "With \$*:"
for arg in "$*"; do
    echo "  [ $arg ]"
done

echo "With \$@:"
for arg in "$@"; do
    echo "  [ $arg ]"
done
EOF

$ chmod +x star.sh
$ ./star.sh one two three
With $*:
  [ one two three ]
With $@:
  [ one ]
  [ two ]
  [ three ]

# ============================================
# PART 9: EXIT STATUS
# ============================================

$ ls /etc/passwd > /dev/null
$ echo $?
[ 0 ]

$ ls /nonexistent > /dev/null 2>&1
$ echo $?
[ 2 ]

# ============================================
# PART 10: DEFAULT VALUES
# ============================================

$ unset NAME
$ echo "${NAME:-Unknown}"
[ Unknown ]

$ NAME="Kronos"
$ echo "${NAME:-Unknown}"
[ Kronos ]

# ============================================
# PART 11: STRING MANIPULATION
# ============================================

$ name="Kronos"
$ echo "Length: ${#name}"
[ Length: 6 ]

$ echo "First 3: ${name:0:3}"
[ First 3: Kro ]

$ echo "Uppercase: ${name^^}"
[ Uppercase: KRONOS ]

$ echo "Lowercase: ${name,,}"
[ Lowercase: kronos ]

# ============================================
# PART 12: EXPORTING
# ============================================

$ EDITOR="nano"
$ bash -c 'echo $EDITOR'
[ ]

$ export EDITOR
$ bash -c 'echo $EDITOR'
[ nano ]

Quick Reference

Declaring and Using Variables

ActionSyntax
DeclareVAR="value"
Access$VAR or ${VAR}
Unsetunset VAR
Exportexport VAR or export VAR=value
Read-onlyreadonly VAR=value
Local (function)local VAR=value
From commandVAR=$(command)
From inputread -p "Prompt: " VAR

Special Variables

VariableMeaning
$0Script name
$1$9Positional parameters
${10}Parameter 10+
$#Number of arguments
$*All args as one word
$@All args as separate words
$$Process ID
$?Exit status of last command
$!PID of last background command
$-Current shell options

Parameter Expansion

SyntaxMeaning
${VAR}Value of VAR
${VAR:-default}Default if unset/empty
${VAR:=default}Set and use default if unset
${VAR:?message}Error if unset
${#VAR}Length
${VAR:offset:length}Substring
${VAR^^}Uppercase
${VAR,,}Lowercase
${VAR#pattern}Remove shortest prefix
${VAR##pattern}Remove longest prefix
${VAR%suffix}Remove shortest suffix
${VAR%%suffix}Remove longest suffix

Exit Codes

CodeMeaning
0Success
1General error
2Misuse
126Not executable
127Command not found
130Ctrl+C

Quoting Rules

FormBehavior
$VARWord splitting, globbing
"$VAR"Literal, no splitting
'$VAR'Literal string, no expansion
"$@"Preserves argument boundaries
"$*"Joins args with first IFS char

Best Practices

Do This:

# No spaces around =
VAR="value"                           # ✅

# Quote variables
echo "$VAR"                           # ✅

# Use braces for clarity
echo "${VAR}_suffix"                  # ✅

# Use "$@" in loops
for arg in "$@"; do ...; done         # ✅

# Provide defaults
echo "${VAR:-default}"                # ✅

# Use local in functions
myfunc() { local tmp="x"; ...; }      # ✅

# Use uppercase for environment variables
export EDITOR="nano"                  # ✅

# Use readonly for constants
readonly MAX=100                      # ✅

# Check exit codes
if command; then ...; fi              # ✅

Don’t Do This:

# Don't put spaces around =
VAR = "value"                         # ❌ command not found

# Don't leave variables unquoted
echo $VAR                             # ⚠️  word splitting

# Don't use $* in loops
for arg in $*; do ...; done           # ❌ breaks on spaces

# Don't forget braces when adjacent to text
echo "$VAR_file"                      # ❌ looks for $VAR_file

# Don't reuse special variable names
1="hello"                             # ❌ invalid

# Don't assume variables persist
# Script variables are local to the script  # ⚠️

# Don't use unset variables without defaults
echo "$UNSET"                         # ⚠️  empty

Common Pitfalls

PitfallProblemSolution
Spaces around =VAR: command not foundVAR=value
Unquoted variableWord splitting"$VAR"
Missing bracesWrong expansion${VAR}_text
$* instead of $@Breaks on spacesUse "$@"
Variable not exportedChild doesn’t see itexport VAR
Local leaksFunction var visiblelocal VAR
Empty unset varSilent bugs${VAR:-default}
CRLF line endingsbad interpreterdos2unix

Real-World Examples

1. Simple Variable

#!/bin/bash
greeting="Hello, World!"
echo "$greeting"
[ Hello, World! ]

2. Use an Argument

#!/bin/bash
name="$1"
echo "Hello, $name!"
[ Hello, Alice! ]

3. Default Value

#!/bin/bash
name="${1:-Guest}"
echo "Hello, $name!"
[ Hello, Guest! ]

4. Command Substitution

#!/bin/bash
today=$(date +%F)
echo "Today is $today"
[ Today is 2024-01-15 ]

5. User Input

#!/bin/bash
read -p "Your name: " name
echo "Hello, $name"
[ Hello, Alice ]

6. Arithmetic

#!/bin/bash
a=5
b=3
echo "$a + $b = $((a + b))"
echo "$a * $b = $((a * b))"
[ 5 + 3 = 8 ]
[ 5 * 3 = 15 ]

7. Check Argument Count

#!/bin/bash
if [ $# -lt 2 ]; then
    echo "Usage: $0 <src> <dst>"
    exit 1
fi
echo "Copying $1 to $2"

8. Loop Over Arguments

#!/bin/bash
for arg in "$@"; do
    echo "Processing: $arg"
done
[ Processing: one ]
[ Processing: two ]
[ Processing: three ]

9. Safe Temp File with $$

#!/bin/bash
TMP="/tmp/script-$$.tmp"
echo "data" > "$TMP"
# ... use it ...
rm -f "$TMP"

10. Check Exit Status

#!/bin/bash
if ! ls /etc/passwd > /dev/null 2>&1; then
    echo "File not found"
    exit 1
fi
echo "File exists"
[ File exists ]

11. String Length and Substring

#!/bin/bash
name="Kronos"
echo "Length: ${#name}"
echo "First 3: ${name:0:3}"
[ Length: 6 ]
[ First 3: Kro ]

12. Uppercase and Lowercase

#!/bin/bash
name="Kronos"
echo "${name^^}"
echo "${name,,}"
[ KRONOS ]
[ kronos ]

13. Strip Extension

#!/bin/bash
file="report.pdf"
base="${file%.pdf}"
echo "$base"
[ report ]

14. Strip Path

#!/bin/bash
path="/home/kronos/docs/report.txt"
file="${path##*/}"
echo "$file"
[ report.txt ]

15. Replace in String

#!/bin/bash
path="/home/kronos/docs/report.txt"
new="${path//\//-}"
echo "$new"
[ -home-kronos-docs-report.txt ]

16. Read Multiple Values

#!/bin/bash
read -p "Enter name and age: " name age
echo "Name: $name, Age: $age"
[ Name: Alice, Age: 30 ]

17. Export for Child Processes

#!/bin/bash
export MY_VAR="shared"
bash -c 'echo "Child sees: $MY_VAR"'
[ Child sees: shared ]

18. Read-Only Variable

#!/bin/bash
readonly MAX=100
echo "Max: $MAX"
# MAX=200    # error: read-only variable
[ Max: 100 ]

19. Use Arrays

#!/bin/bash
fruits=("apple" "banana" "cherry")
echo "${fruits[0]}"
echo "${fruits[@]}"
echo "${#fruits[@]}"
[ apple ]
[ apple banana cherry ]
[ 3 ]

20. Associative Arrays (Bash 4+)

#!/bin/bash
declare -A user
user[name]="Kronos"
user[uid]=1000
echo "${user[name]}"
echo "${user[uid]}"
[ Kronos ]
[ 1000 ]

Visual: Variable Lifecycle

┌──────────────────────────────────────────────┐
│           Variable lifecycle                 │
│                                              │
│  1. Declare:                                 │
│     name="Kronos"                            │
│         │                                    │
│         ▼                                    │
│  2. Use:                                     │
│     echo "Hello, $name"                      │
│     → [ Hello, Kronos ]                      │
│         │                                    │
│         ▼                                    │
│  3. Modify:                                  │
│     name="Alice"                             │
│     echo "$name"                             │
│     → [ Alice ]                              │
│         │                                    │
│         ▼                                    │
│  4. Unset:                                   │
│     unset name                               │
│     echo "$name"                             │
│     → [ ]                                    │
│                                              │
└──────────────────────────────────────────────┘

Visual: $* vs $@

┌──────────────────────────────────────────────┐
│  Script called: ./test.sh one two three      │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  "$*"                                  │  │
│  │  → [ one two three ]                   │  │
│  │  (one string)                          │  │
│  └────────────────────────────────────────┘  │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  "$@"                                  │  │
│  │  → [ one ] [ two ] [ three ]           │  │
│  │  (three separate strings)              │  │
│  └────────────────────────────────────────┘  │
│                                              │
│  Use "$@" to preserve argument boundaries    │
│                                              │
└──────────────────────────────────────────────┘

Summary

ConceptSyntaxExample
DeclareVAR="value"name="Kronos"
Access$VARecho "$name"
Braces${VAR}echo "${name}_file"
Unsetunset VARunset name
Exportexport VARexport EDITOR
From commandVAR=$(cmd)TODAY=$(date +%F)
From inputread VARread -p "Name: " name
Arithmetic$((...))$((a + b))
Default${VAR:-def}${NAME:-Guest}
Length${#VAR}${#name}
Substring${VAR:o:l}${name:0:3}
Uppercase${VAR^^}${name^^}
Lowercase${VAR,,}${name,,}
Script name$0echo "$0"
Arg 1$1echo "$1"
Arg count$#echo "$#"
All args"$@"for a in "$@"; do
Process ID$$/tmp/x-$$.tmp
Exit status$?echo "$?"
Background PID$!kill $!

Key takeaways:

  • Declare with VAR="value"no spaces around =
  • Access with $VAR or ${VAR} — braces avoid ambiguity
  • Always quote variables — "$VAR" — unless you want word splitting
  • Use $(command) for command substitution
  • Use unset VAR to remove a variable
  • Use export VAR to make it visible to child processes
  • Special variables are provided by the shell — $0, $1$9, $#, $*, $@, $$, $?
  • Use "$@" in loops and functions — it preserves argument boundaries
  • Use $? to check the exit status of the last command
  • Use $$ for unique temp filenames
  • Use ${VAR:-default} to provide fallbacks
  • Use ${#VAR}, ${VAR:o:l}, ${VAR^^}, ${VAR,,} for string manipulation
  • Use local inside functions to avoid leaks
  • Use readonly for constants

Remember: Variables are how scripts remember things. Declare them without spaces, use them with $, quote them always. Special variables like $1, $#, $@, $?, and $$ give your script awareness of its arguments, its environment, and its own execution. Learn parameter expansion — ${VAR:-default}, ${#VAR}, ${VAR^^} — and you can do most string manipulation without calling external tools. Master variables, and you’ve mastered the core of shell scripting.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!