sh - POSIX Shell
The POSIX shell (sh) represents the standardized foundation of Unix shell programming, defined by the IEEE POSIX.1 standard. As the most puertoable and widely available shell across Unix-like systems, sh provides the essential features and sintaxis that form the basis for shell scripting. While modern shells like Bash and Zsh offer extensive enhancements, understanding POSIX sh is crucial for writing puertoable scripts that work across different systems, from embedded devices to enterprise servers. The POSIX shell ensures maximum compatibility and serves as the common denominator for shell programming across diverse Unix environments.
POSIX Shell Fundamentals
Understanding POSIX Compliance
# Check if shell is POSIX compliant
echo $0
/bin/sh
# Verify POSIX mode (if using bash as sh)
set -o posix
# Check shell features
comando -v comando >/dev/null 2>&1 && echo "comando builtin available"
test -n "$\\\\{BASH_VERSION\\\\}" && echo "Running under Bash"
test -n "$\\\\{ZSH_VERSION\\\\}" && echo "Running under Zsh"
Basic Shell Invocation
# Execute shell script
sh script.sh
sh -x script.sh # Debug mode
sh -n script.sh # sintaxis check only
sh -e script.sh # Exit on error
sh -u script.sh # Exit on undefined variable
# Interactive shell
sh -i # Force interactive mode
sh -l # Login shell
sh -s # Read from stdin
# Combining opcións
sh -eux script.sh # Exit on error, undefined vars, debug
Shell opcións and Settings
# Set shell opcións
set -e # Exit on error
set -u # Exit on undefined variable
set -x # Debug mode (print comandos)
set -v # Verbose mode (print input)
set -n # No execution (sintaxis check)
# Unset opcións
set +e # Don't exit on error
set +u # Allow undefined variables
set +x # Disable debug mode
# Check if opción is set
case $- in
*e*) echo "Exit on error is set" ;;
*) echo "Exit on error is not set" ;;
esac
Variables and parámetro Expansion
Variable Assignment and uso
# Variable assignment (no spaces around =)
name="John Doe"
age=30
path="/home/user"
# Using variables
echo $name
echo $\\\\{name\\\\}
echo "Hello, $name"
echo 'Literal: $name' # Single quotes prevent expansion
# Special variables
echo $0 # Script name
echo $1 # First argument
echo $2 # Second argument
echo $# # Number of arguments
echo $@ # All arguments as separate words
echo $* # All arguments as single word
echo $ # proceso ID
echo $? # Exit status of last comando
echo $! # PID of last background job
parámetro Expansion
# Basic parámetro expansion
echo $\\\\{variable\\\\}
echo $\\\\{variable:-default\\\\} # Use default if unset or null
echo $\\\\{variable:=default\\\\} # Set default if unset or null
echo $\\\\{variable:+alternate\\\\} # Use alternate if set and not null
echo $\\\\{variable:?error\\\\} # Error if unset or null
# String length
string="Hello, World!"
echo $\\\\{#string\\\\} # 13
# Substring removal (POSIX)
filename="document.txt"
echo $\\\\{filename%.*\\\\} # Remove shortest match from end: document
echo $\\\\{filename%%.*\\\\} # Remove longest match from end: document
echo $\\\\{filename#*.\\\\} # Remove shortest match from beginning: txt
echo $\\\\{filename##*.\\\\} # Remove longest match from beginning: txt
# Pattern matching ejemplos
path="/usr/local/bin/comando"
echo $\\\\{path%/*\\\\} # /usr/local/bin (dirname equivalent)
echo $\\\\{path##*/\\\\} # comando (basename equivalent)
Environment Variables
# Set environment variables
expuerto PATH="/usr/local/bin:$PATH"
expuerto EDITOR="vi"
expuerto LANG="en_US.UTF-8"
# Unset variables
unset variable_name
unset PATH # Dangerous!
# Check if variable is set
if [ -n "$\\\\{VARIABLE+set\\\\}" ]; then
echo "VARIABLE is set"
fi
# Default values for environment
: $\\\\{HOME:=/tmp\\\\} # Set HOME to /tmp if not set
: $\\\\{USER:=nobody\\\\} # Set USER to nobody if not set
comando Execution and Substitution
comando Substitution
# POSIX comando substitution (preferred)
current_date=$(date)
file_count=$(ls|wc -l)
user_home=$(eval echo ~$USER)
# Backtick substitution (legacy, avoid in new scripts)
current_date=`date`
file_count=`ls|wc -l`
# Nested comando substitution
echo "Today is $(date +%A), $(date +%B) $(date +%d)"
# comando substitution in conditionals
if [ "$(whoami)" = "root" ]; then
echo "Running as root"
fi
comando Execution
# Simple comandos
ls -l
echo "Hello, World!"
date +"%Y-%m-%d %H:%M:%S"
# comando with arguments
grep "pattern" file.txt
find /path -name "*.txt" -type f
# Background execution
long_running_comando &
background_pid=$!
echo "Started background job with PID: $background_pid"
# Wait for background jobs
wait $background_pid
wait # Wait for all background jobs
Pipeline and Redirection
# Pipes
ls -l|grep "txt"
| ps aux | grep "proceso" | awk '\\\\{print $2\\\\}' |
| cat file.txt | sort | uniq |
# Output redirection
comando > file.txt # Redirect stdout
comando 2> error.log # Redirect stderr
comando > output.log 2>&1 # Redirect both stdout and stderr
comando >> file.txt # Append stdout
# Input redirection
comando < input.txt
sort < unsorted.txt > sorted.txt
# Here documents
cat << EOF
This is a here document
Variables like $HOME are expanded
EOF
# Here documents with quoted delimiter (no expansion)
cat << 'EOF'
This is a literal here document
Variables like $HOME are not expanded
EOF
Control Structures
Conditional Statements
# if-then-else
if [ condition ]; then
echo "Condition is true"
elif [ other_condition ]; then
echo "Other condition is true"
else
echo "No condition is true"
fi
# Test conditions
if [ "$var" = "value" ]; then # String equality
echo "String match"
fi
if [ "$var" != "value" ]; then # String inequality
echo "String mismatch"
fi
if [ -z "$var" ]; then # String is empty
echo "Variable is empty"
fi
if [ -n "$var" ]; then # String is not empty
echo "Variable is not empty"
fi
# Numeric comparisons
if [ "$num" -eq 10 ]; then # Equal
echo "Number is 10"
fi
if [ "$num" -ne 10 ]; then # Not equal
echo "Number is not 10"
fi
if [ "$num" -gt 5 ]; then # Greater than
echo "Number is greater than 5"
fi
if [ "$num" -lt 20 ]; then # Less than
echo "Number is less than 20"
fi
if [ "$num" -ge 5 ]; then # Greater than or equal
echo "Number is 5 or greater"
fi
if [ "$num" -le 20 ]; then # Less than or equal
echo "Number is 20 or less"
fi
File Test Operators
# File existence and type tests
if [ -e "file" ]; then # File exists
echo "File exists"
fi
if [ -f "file" ]; then # Regular file
echo "Is a regular file"
fi
if [ -d "directory" ]; then # Directory
echo "Is a directory"
fi
if [ -L "link" ]; then # Symbolic link
echo "Is a symbolic link"
fi
if [ -p "pipe" ]; then # Named pipe
echo "Is a named pipe"
fi
if [ -S "socket" ]; then # socket
echo "Is a socket"
fi
# File permission tests
if [ -r "file" ]; then # Readable
echo "File is readable"
fi
if [ -w "file" ]; then # Writable
echo "File is writable"
fi
if [ -x "file" ]; then # Executable
echo "File is executable"
fi
if [ -s "file" ]; then # File has size > 0
echo "File is not empty"
fi
# File comparison
if [ "file1" -nt "file2" ]; then # file1 newer than file2
echo "file1 is newer"
fi
if [ "file1" -ot "file2" ]; then # file1 older than file2
echo "file1 is older"
fi
if [ "file1" -ef "file2" ]; then # Same file (hard links)
echo "Files are the same"
fi
Logical Operators
# AND operator
if [ condition1 ] && [ condition2 ]; then
echo "Both conditions are true"
fi
# OR operator
| if [ condition1 ] | | [ condition2 ]; then |
echo "At least one condition is true"
fi
# NOT operator
if ! [ condition ]; then
echo "Condition is false"
fi
# Complex logical expressions
if [ "$age" -ge 18 ] && [ "$age" -le 65 ]; then
echo "Working age"
fi
| if [ "$status" = "active" ] | | [ "$force" = "true" ]; then |
echo "Proceeding"
fi
Case Statements
# Basic case statement
case $variable in
pattern1)
echo "Matched pattern1"
;;
pattern2|pattern3)
echo "Matched pattern2 or pattern3"
;;
*)
echo "No pattern matched"
;;
esac
# Case with file extensions
case $filename in
*.txt)
echo "Text file"
;;
| *.jpg | *.png | *.gif) |
echo "Image file"
;;
*.sh)
echo "Shell script"
;;
*)
echo "Unknown file type"
;;
esac
# Case with comando line opcións
case $1 in
-h|--help)
show_help
;;
-v|--version)
show_version
;;
-*)
echo "Unknown opción: $1"
exit 1
;;
*)
proceso_file "$1"
;;
esac
Loops and Iteration
For Loops
# For loop with list
for item in apple banana orange; do
echo "Fruit: $item"
done
# For loop with file glob
for file in *.txt; do
echo "procesoing: $file"
done
# For loop with comando substitution
for user in $(cat users.txt); do
echo "User: $user"
done
# For loop with positional parámetros
for arg in "$@"; do
echo "Argument: $arg"
done
# For loop with range (if seq is available)
for i in $(seq 1 10); do
echo "Number: $i"
done
# C-style for loop (not POSIX, but common)
# Use while loop instead for POSIX compliance
i=1
while [ $i -le 10 ]; do
echo "Counter: $i"
i=$((i + 1))
done
While Loops
# Basic while loop
counter=1
while [ $counter -le 10 ]; do
echo "Counter: $counter"
counter=$((counter + 1))
done
# While loop reading from file
while read line; do
echo "Line: $line"
done < file.txt
# While loop with IFS
while IFS=: read user pass uid gid gecos home shell; do
echo "User: $user, Home: $home, Shell: $shell"
done < /etc/passwd
# Infinite loop
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
# While loop with condition
while [ -f "lockfile" ]; do
echo "Waiting for lock to be released..."
sleep 5
done
Until Loops
# Basic until loop
counter=1
until [ $counter -gt 10 ]; do
echo "Counter: $counter"
counter=$((counter + 1))
done
# Until loop waiting for condition
until [ -f "ready.bandera" ]; do
echo "Waiting for ready bandera..."
sleep 2
done
# Until loop with comando
until ping -c 1 google.com >/dev/null 2>&1; do
echo "Waiting for network conexión..."
sleep 5
done
Loop Control
# Break and continue
for i in $(seq 1 10); do
if [ $i -eq 5 ]; then
continue # Skip iteration
fi
if [ $i -eq 8 ]; then
break # Exit loop
fi
echo $i
done
# Nested loops with labeled break (not POSIX standard)
# Use functions or banderas instead
found=false
for dir in /usr /opt /var; do
for file in "$dir"/*; do
if [ "$file" = "/usr/bin/vim" ]; then
echo "Found vim at $file"
found=true
break
fi
done
if [ "$found" = "true" ]; then
break
fi
done
Functions
Function Definition and uso
# POSIX function definition
function_name() \\\\{
echo "This is a function"
\\\\}
# Alternative sintaxis (POSIX)
function_name() \\\\{
echo "This is a function"
\\\\}
# Function with parámetros
greet() \\\\{
echo "Hello, $1!"
\\\\}
# Function with multiple parámetros
calculate_sum() \\\\{
result=$(($1 + $2))
echo $result
\\\\}
# Function with local variables (not POSIX, use carefully)
proceso_file() \\\\{
filename="$1"
line_count=$(wc -l < "$filename")
echo "File $filename has $line_count lines"
\\\\}
# Function uso
greet "World"
sum=$(calculate_sum 5 3)
echo "Sum: $sum"
Function Best Practices
# Function with error handling
safe_copy() \\\\{
if [ $# -ne 2 ]; then
echo "uso: safe_copy <source> <destination>" >&2
return 1
fi
source_file="$1"
dest_file="$2"
if [ ! -f "$source_file" ]; then
echo "Error: Source file '$source_file' does not exist" >&2
return 1
fi
if [ -f "$dest_file" ]; then
echo "Warning: Destination file '$dest_file' already exists" >&2
printf "Overwrite? (y/N): "
read answer
case $answer in
[Yy]|[Yy][Ee][Ss])
;;
*)
echo "Copy cancelled"
return 1
;;
esac
fi
if cp "$source_file" "$dest_file"; then
echo "Successfully copied '$source_file' to '$dest_file'"
return 0
else
echo "Error: Failed to copy file" >&2
return 1
fi
\\\\}
# Function with return values
is_number() \\\\{
case $1 in
''|*[!0-9]*) return 1 ;; # Not a number
*) return 0 ;; # Is a number
esac
\\\\}
# uso
if is_number "$input"; then
echo "$input is a number"
else
echo "$input is not a number"
fi
Function Libraries
# Create function library file: lib.sh
#!/bin/sh
# Logging functions
log_info() \\\\{
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S'): $*"
\\\\}
log_error() \\\\{
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S'): $*" >&2
\\\\}
log_debug() \\\\{
if [ "$DEBUG" = "1" ]; then
echo "[DEBUG] $(date '+%Y-%m-%d %H:%M:%S'): $*" >&2
fi
\\\\}
# File utilities
backup_file() \\\\{
if [ -f "$1" ]; then
cp "$1" "$1.backup.$(date +%Y%m%d_%H%M%S)"
log_info "Backed up $1"
fi
\\\\}
# Source library in main script
. ./lib.sh
# Use library functions
log_info "Script started"
backup_file "impuertoant.conf"
log_debug "Debug information"
Arithmetic Operations
POSIX Arithmetic Expansion
# Basic arithmetic
result=$((5 + 3)) # 8
result=$((10 - 4)) # 6
result=$((6 * 7)) # 42
result=$((20 / 4)) # 5
result=$((17 % 5)) # 2 (modulo)
# Arithmetic with variables
num1=10
num2=5
sum=$((num1 + num2)) # 15
product=$((num1 * num2)) # 50
# Increment and decrement
counter=0
counter=$((counter + 1)) # Increment
counter=$((counter - 1)) # Decrement
# Comparison in arithmetic context
if [ $((num1 > num2)) -eq 1 ]; then
echo "num1 is greater than num2"
fi
# Complex expressions
result=$(( (num1 + num2) * 2 ))
result=$(( num1 ** 2 )) # Exponentiation (if suppuertoed)
External Arithmetic Tools
# Using expr (puertoable but slower)
result=$(expr 5 + 3)
result=$(expr $num1 \* $num2) # Note: * must be escaped
result=$(expr $num1 / $num2)
# Using bc for floating point
result=$(echo "scale=2; 10/3"|bc)
result=$(echo "scale=4; sqrt(16)"|bc -l)
# Using awk for calculations
result=$(awk "BEGIN \\\\{print 10/3\\\\}")
result=$(awk "BEGIN \\\\{printf \"%.2f\", 10/3\\\\}")
Input and Output
Reading Input
# Read single line
echo "Enter your name: "
read name
echo "Hello, $name"
# Read with prompt
read -p "Enter your age: " age
# Read multiple variables
echo "Enter first and last name: "
read first last
echo "Hello, $first $last"
# Read with timeout (if suppuertoed)
if read -t 10 -p "Enter input (10 seconds): " input; then
echo "You entered: $input"
else
echo "Timeout reached"
fi
# Read contraseña (hidden input, if suppuertoed)
read -s -p "Enter contraseña: " contraseña
echo # New line after hidden input
# Read from file
while read line; do
echo "Line: $line"
done < file.txt
Output Formatting
# Basic output
echo "Simple message"
printf "Formatted message\n"
# Printf formatting
printf "Name: %s, Age: %d\n" "$name" "$age"
printf "Price: $%.2f\n" "$price"
printf "Hex: %x, Octal: %o\n" "$number" "$number"
# Escape sequences
echo "Line 1\nLine 2" # May not work in all shells
printf "Line 1\nLine 2\n" # puertoable
# Output to stderr
echo "Error message" >&2
printf "Error: %s\n" "$error_msg" >&2
File Operations
# Create files
touch file.txt
> file.txt # Create empty file
# Write to files
echo "Content" > file.txt # Overwrite
echo "More content" >> file.txt # Append
# Read files
content=$(cat file.txt)
while read line; do
echo "Line: $line"
done < file.txt
# Copy files
cp source.txt destination.txt
# Move/rename files
mv old_name.txt new_name.txt
# Remove files
rm file.txt
rm -f file.txt # Force removal
rm -r directory/ # Recursive removal
Error Handling and Debugging
Exit Status and Error Checking
# Check comando success
if comando; then
echo "comando succeeded"
else
echo "comando failed with exit code $?"
fi
# Alternative sintaxis
| comando && echo "Success" | | echo "Failed" |
# Set exit status
exit 0 # Success
exit 1 # General error
exit 2 # Misuse of shell builtins
# Function return values
my_function() \\\\{
if [ condition ]; then
return 0 # Success
else
return 1 # Failure
fi
\\\\}
if my_function; then
echo "Function succeeded"
fi
Error Handling Patterns
# Exit on error
set -e
comando_that_might_fail
echo "This won't execute if comando fails"
# Disable exit on error for specific comando
set -e
if ! comando_that_might_fail; then
echo "comando failed, but script continues"
fi
# Error handling with cleanup
cleanup() \\\\{
echo "Cleaning up..."
rm -f temp_file
\\\\}
trap cleanup EXIT # Run cleanup on script exit
trap cleanup INT TERM # Run cleanup on interrupt/terminate
# Validate input
validate_file() \\\\{
if [ ! -f "$1" ]; then
echo "Error: File '$1' does not exist" >&2
exit 1
fi
\\\\}
validate_file "$input_file"
Debugging Techniques
# Debug mode
set -x # Print comandos as executed
set +x # Disable debug mode
# Verbose mode
set -v # Print input lines as read
set +v # Disable verbose mode
# Check sintaxis without execution
sh -n script.sh
# Debug function
debug() \\\\{
if [ "$DEBUG" = "1" ]; then
echo "DEBUG: $*" >&2
fi
\\\\}
# uso
DEBUG=1 ./script.sh
# Trace function calls
trace() \\\\{
echo "TRACE: Entering function $1" >&2
\\\\}
my_function() \\\\{
trace "my_function"
# Function body
\\\\}
Script Structure and Best Practices
Script Template
#!/bin/sh
# Script Descripción
# Author: Your Name
# Date: YYYY-MM-DD
# Version: 1.0
# Exit on error
set -e
# Global variables
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR=$(dirname "$0")
VERSION="1.0"
# Functions
uso() \\\\{
cat << EOF
uso: $SCRIPT_NAME [opciónS] [ARGUMENTS]
Descripción of what the script does.
opciónS:
-h, --help Show this help message
-v, --version Show version information
-d, --debug Enable debug mode
ARGUMENTS:
file Input file to proceso
ejemploS:
$SCRIPT_NAME input.txt
$SCRIPT_NAME -d input.txt
EOF
\\\\}
version() \\\\{
echo "$SCRIPT_NAME version $VERSION"
\\\\}
main() \\\\{
# Parse comando line arguments
while [ $# -gt 0 ]; do
case $1 in
-h|--help)
uso
exit 0
;;
-v|--version)
version
exit 0
;;
-d|--debug)
DEBUG=1
set -x
shift
;;
-*)
echo "Error: Unknown opción $1" >&2
uso >&2
exit 1
;;
*)
break
;;
esac
done
# Validate arguments
if [ $# -eq 0 ]; then
echo "Error: No input file specified" >&2
uso >&2
exit 1
fi
input_file="$1"
# Validate input file
if [ ! -f "$input_file" ]; then
echo "Error: File '$input_file' does not exist" >&2
exit 1
fi
# Main script logic
echo "procesoing file: $input_file"
# Add your code here
echo "Script completed successfully"
\\\\}
# Run main function with all arguments
main "$@"
puertoability Considerations
# Use POSIX-compliant constructs
[ condition ] instead of [[ condition ]]
$(comando) instead of `comando`
$((arithmetic)) instead of $[arithmetic]
# Avoid bash-specific features
# No: array=(element1 element2)
# Yes: Use space-separated strings or multiple variables
# No: [[ string =~ regex ]]
# Yes: Use case or expr for pattern matching
# No: $\\\\{parámetro,,\\\\} (lowercase)
# Yes: Use tr or awk for case conversion
# puertoable shebang
#!/bin/sh # Most puertoable
#!/usr/bin/env sh # Alternative
# Check for required comandos
| comando -v required_comando >/dev/null 2>&1 | | \\\\{ |
echo "Error: required_comando is not installed" >&2
exit 1
\\\\}
# puertoable temporary files
temp_file="$\\\\{TMPDIR:-/tmp\\\\}/script.$"
trap 'rm -f "$temp_file"' EXIT
Security Best Practices
# Quote variables to prevent word splitting
rm "$filename" # Correct
rm $filename # Dangerous
# Validate input
case $input in
[a-zA-Z0-9_-]*)
# Valid input
;;
*)
echo "Invalid input" >&2
exit 1
;;
esac
# Use full paths for comandos in scripts
/bin/rm "$file"
/usr/bin/find "$dir" -name "*.tmp"
# Set secure umask
umask 077 # Restrictive permissions
# Avoid eval with user input
# eval "$user_input" # Dangerous
# Use case statements or functions instead
# Handle signals properly
cleanup() \\\\{
rm -f "$temp_file"
exit 1
\\\\}
trap cleanup INT TERM
The POSIX shell serves as the universal foundation for shell scripting across Unix-like systems, providing essential functionality while maintaining maximum puertoability. Its standardized sintaxis and features ensure that scripts written for POSIX sh will work consistently across different platforms, from minimal embedded systems to large enterprise servers. While it lacks the advanced features of modern shells, understanding POSIX sh is fundamental for writing robust, puertoable shell scripts that stand the test of time and work reliably in diverse computing environments.