Objective: Generate SSH keys and configure password-less login.
Task 1: Generate Key Pair
Step 1: Generate keys
ssh-keygenInteractive prompts:
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): [Press Enter - use default]
Created directory '/home/user/.ssh'.
Enter passphrase (empty for no passphrase): [Press Enter - no password]
Enter same passphrase again: [Press Enter]
Output:
Your identification has been saved in /home/user/.ssh/id_rsa
Your public key has been saved in /home/user/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:abc123... user@computer
Verify keys created:
ls -la ~/.ssh/Output:
-rw------- 1 user user 2602 Dec 16 14:30 id_rsa # Private key
-rw-r--r-- 1 user user 571 Dec 16 14:30 id_rsa.pub # Public key
Task 2: Copy Key to Server (Using SSH, Not SCP)
Method: Manual with SSH
Step 1: View your public key
cat ~/.ssh/id_rsa.pubCopy the entire output (looks like):
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... user@computer
Step 2: SSH to server (with password)
ssh student@server
# Enter password when promptedStep 3: Create .ssh directory on server
mkdir -p ~/.ssh
chmod 700 ~/.sshStep 4: Add public key
vim ~/.ssh/authorized_keys
# Paste the public key (one line)
# Save and exit (:wq)Step 5: Set correct permissions
chmod 600 ~/.ssh/authorized_keysStep 6: Exit and test
exit # Logout from server
# Try connecting again
ssh student@server
# Should connect WITHOUT asking for password!Alternative: One-liner (still using ssh, not scp)
From local machine:
cat ~/.ssh/id_rsa.pub | ssh student@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"Explanation:
cat ~/.ssh/id_rsa.pub- Read public key|- Pipe to sshssh student@server "commands"- Execute commands on servermkdir -p ~/.ssh- Create directorycat >> ~/.ssh/authorized_keys- Append piped keychmod- Set correct permissions
Objective: Create script k that evaluates arithmetic expressions with floating point.
Solution:
vim ~/bin/kScript content:
#!/bin/bash
# k - Calculator using bc
# Check if argument provided
if [ $# -eq 0 ]; then
echo "Usage: k 'expression'" >&2
echo "Example: k '2.2*9'" >&2
exit 1
fi
# Evaluate expression using bc
result=$(echo "scale=10; $1" | bc)
# Output result
echo "$result"Make executable:
chmod +x ~/bin/kUsage examples:
k '2.2*9'
# Output: 19.8
k '10/3'
# Output: 3.3333333333
k '2^10'
# Output: 1024
k 'sqrt(16)'
# Output: 4
k '(5+3)*2/4'
# Output: 4.0000000000Enhanced version with better output:
#!/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: k 'expression'" >&2
exit 1
fi
# Calculate with scale=10
result=$(echo "scale=10; $1" | bc)
# Remove trailing zeros
result=$(echo "$result" | sed 's/\.0*$//' | sed 's/\.\([0-9]*[1-9]\)0*$/.\1/')
echo "$result"Tests:
k '10/3' # 3.3333333333
k '5/2' # 2.5
k '4/2' # 2 (not 2.0)Objective: Script that logs system info every 3 seconds, handles signals.
Solution:
vim ~/monitor.shScript content:
#!/bin/bash
# System monitor script
# Configuration
LOGFILE=~/myps.log
# Signal handlers
handle_sigusr1() {
# Clean file and notify
> "$LOGFILE"
echo "File $LOGFILE cleaned" >&2
}
# Trap signals
trap '' SIGINT SIGTERM # Ignore Ctrl+C and kill
trap 'handle_sigusr1' SIGUSR1 # Custom handler for SIGUSR1
# Main loop
while true; do
# Get current date/time
datetime=$(date '+%Y-%m-%d %H:%M:%S')
# Get number of processes
process_count=$(ps aux --no-headers | wc -l)
# Get load average (1, 5, 15 minutes)
load=$(uptime | awk -F'load average:' '{print $2}' | tr -d ' ')
load1=$(echo $load | cut -d',' -f1)
load5=$(echo $load | cut -d',' -f2)
load15=$(echo $load | cut -d',' -f3)
# Log entry
echo "$datetime,$process_count,$load1,$load5,$load15" >> "$LOGFILE"
# Wait 3 seconds
sleep 3
doneMake executable:
chmod +x ~/monitor.shUsage:
Start monitoring:
~/monitor.sh &
[1] 12345Check log file:
tail ~/myps.logOutput:
2024-12-16 14:30:00,234,0.15,0.18,0.20
2024-12-16 14:30:03,235,0.14,0.17,0.19
2024-12-16 14:30:06,233,0.16,0.18,0.20
Test signal resistance:
# Try Ctrl+C (script keeps running)
fg
Ctrl+C # Ignored!
Ctrl+Z
bgClean log file:
kill -SIGUSR1 12345Check stderr:
File /home/student/myps.log cleaned
Stop script:
kill -9 12345 # Only force kill worksObjective: Convert JPG to PNG concurrently with error handling.
Solution:
vim ~/convert_images.shScript content:
#!/bin/bash
# Parallel image converter JPG to PNG
VERBOSE=0
TARGET_DIR=""
# Parse options
while [ $# -gt 0 ]; do
case "$1" in
-v)
VERBOSE=1
shift
;;
*)
if [ -z "$TARGET_DIR" ]; then
TARGET_DIR="$1"
fi
shift
;;
esac
done
# Check if directory provided
if [ -z "$TARGET_DIR" ]; then
echo "Usage: $0 [-v] directory" >&2
exit 1
fi
# Check if directory exists
if [ ! -d "$TARGET_DIR" ]; then
echo "Error: Directory $TARGET_DIR does not exist" >&2
exit 1
fi
# Function to convert single image
convert_image() {
local file="$1"
local verbose="$2"
# Get base name
local basename=$(basename "$file" .jpg)
local dirname=$(dirname "$file")
local output="$dirname/${basename}.png"
# Check if file is JPEG
if ! file "$file" | grep -q "JPEG"; then
echo "Error: $file is not a JPEG image" >&2
return 1
fi
# Check if output already exists
if [ -e "$output" ]; then
echo "Error: $output already exists, skipping" >&2
return 1
fi
# Verbose output
if [ "$verbose" -eq 1 ]; then
echo "Converting: $file"
fi
# Convert
convert "$file" "$output"
return 0
}
# Start timer if verbose
if [ "$VERBOSE" -eq 1 ]; then
START_TIME=$(date +%s.%N)
fi
# Find all JPG files and convert concurrently
pids=()
for file in "$TARGET_DIR"/*.jpg "$TARGET_DIR"/*.JPG; do
# Skip if glob didn't match
[ -e "$file" ] || continue
# Convert in background
convert_image "$file" "$VERBOSE" &
pids+=($!)
done
# Wait for all conversions
for pid in "${pids[@]}"; do
wait "$pid"
done
# Show timing if verbose
if [ "$VERBOSE" -eq 1 ]; then
END_TIME=$(date +%s.%N)
DURATION=$(echo "scale=1; $END_TIME - $START_TIME" | bc)
echo "Total conversion time: ${DURATION}s"
fiMake executable:
chmod +x ~/convert_images.shUsage examples:
Setup test images:
mkdir ~/test_images
# Upload some JPG files to ~/test_imagesConvert without verbose:
~/convert_images.sh ~/test_imagesConvert with verbose:
~/convert_images.sh -v ~/test_imagesOutput:
Converting: /home/student/test_images/photo1.jpg
Converting: /home/student/test_images/photo2.jpg
Converting: /home/student/test_images/photo3.jpg
Total conversion time: 2.3s
Verbose as second parameter:
~/convert_images.sh ~/test_images -vError handling:
# Try converting non-JPEG
cp /etc/passwd ~/test_images/fake.jpg
~/convert_images.sh ~/test_images
# Output: Error: /home/student/test_images/fake.jpg is not a JPEG imageTry converting again (already exists):
~/convert_images.sh ~/test_images
# Output: Error: /home/student/test_images/photo1.png already exists, skippingObjective: Trace script execution.
Using set -v:
vim ~/convert_debug_v.sh#!/bin/bash
set -v
# Shows commands as written
TARGET_DIR="$1"
for file in "$TARGET_DIR"/*.jpg; do
echo "Processing: $file"
doneRun:
chmod +x ~/convert_debug_v.sh
~/convert_debug_v.sh ~/test_imagesOutput shows literal commands:
TARGET_DIR="$1"
for file in "$TARGET_DIR"/*.jpg; do
echo "Processing: $file"
done
Processing: /home/student/test_images/photo1.jpg
Using set -x:
vim ~/convert_debug_x.sh#!/bin/bash
set -x
# Shows expanded commands
TARGET_DIR="$1"
for file in "$TARGET_DIR"/*.jpg; do
echo "Processing: $file"
doneRun:
chmod +x ~/convert_debug_x.sh
~/convert_debug_x.sh ~/test_imagesOutput shows variable expansion:
+ TARGET_DIR=/home/student/test_images
+ for file in "$TARGET_DIR"/*.jpg
+ echo 'Processing: /home/student/test_images/photo1.jpg'
Processing: /home/student/test_images/photo1.jpg
Objective: Monitor directory for new files, handle multiple signals.
Solution:
vim ~/monitor_dir.shScript content:
#!/bin/bash
# Directory monitor with signal handling
# Check arguments
if [ $# -lt 1 ]; then
echo "Usage: $0 directory" >&2
exit 1
fi
MONITOR_DIR="$1"
# Check if directory exists
if [ ! -d "$MONITOR_DIR" ]; then
echo "Error: Directory $MONITOR_DIR does not exist" >&2
exit 1
fi
# Create temporary files
TEMP_LOG=$(mktemp)
TEMP_FILES_LIST=$(mktemp)
echo "Temporary files created:"
echo "Log: $TEMP_LOG"
echo "Files list: $TEMP_FILES_LIST"
# Signal handlers
show_files_list() {
echo "=== Files List ==="
cat "$TEMP_FILES_LIST"
echo "=================="
}
show_log() {
echo "=== Event Log ==="
cat "$TEMP_LOG"
echo "================="
}
cleanup_and_exit() {
echo "Cleaning up and exiting..."
rm -f "$TEMP_LOG" "$TEMP_FILES_LIST"
exit 0
}
# Trap signals
trap 'show_files_list' SIGUSR1
trap 'show_log' SIGUSR2
trap 'cleanup_and_exit' SIGINT SIGTERM
# Initial snapshot
current_files=$(find "$MONITOR_DIR" -type f 2>/dev/null | sort)
echo "Monitoring $MONITOR_DIR for new files..."
echo "PID: $$"
echo "Send SIGUSR1 to see files list: kill -SIGUSR1 $$"
echo "Send SIGUSR2 to see event log: kill -SIGUSR2 $$"
echo "Press Ctrl+C to stop"
# Main monitoring loop
while true; do
sleep 5
# Get current files
new_snapshot=$(find "$MONITOR_DIR" -type f 2>/dev/null | sort)
# Find new files
new_files=$(comm -13 <(echo "$current_files") <(echo "$new_snapshot"))
# Process new files
if [ -n "$new_files" ]; then
while IFS= read -r filepath; do
filename=$(basename "$filepath")
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# Add to files list
echo "$filename" >> "$TEMP_FILES_LIST"
# Log event
echo "$timestamp: Dodano plik: $filename" >> "$TEMP_LOG"
echo "New file detected: $filename"
done <<< "$new_files"
fi
# Update snapshot
current_files="$new_snapshot"
doneMake executable:
chmod +x ~/monitor_dir.shUsage:
Start monitoring:
~/monitor_dir.sh ~/Documents &Output:
Temporary files created:
Log: /tmp/tmp.Ab12cD
Files list: /tmp/tmp.Xy34Zw
Monitoring /home/student/Documents for new files...
PID: 12345
Send SIGUSR1 to see files list: kill -SIGUSR1 12345
Send SIGUSR2 to see event log: kill -SIGUSR2 12345
Press Ctrl+C to stop
[1] 12345
Create new files:
touch ~/Documents/test1.txt
sleep 6
touch ~/Documents/test2.txtView files list:
kill -SIGUSR1 12345Output:
=== Files List ===
test1.txt
test2.txt
==================
View event log:
kill -SIGUSR2 12345Output:
=== Event Log ===
2024-12-16 14:35:00: Dodano plik: test1.txt
2024-12-16 14:35:06: Dodano plik: test2.txt
=================
Stop monitoring:
kill 12345
# Or bring to foreground and Ctrl+C
fg
Ctrl+COutput:
Cleaning up and exiting...
How to check which new files appeared in directory since last check?
Method 1: Using find with timestamp comparison
# Store initial snapshot
initial_files=$(find /path/to/dir -type f)
# ... time passes ...
# Get current snapshot
current_files=$(find /path/to/dir -type f)
# Find new files
new_files=$(comm -13 <(echo "$initial_files" | sort) <(echo "$current_files" | sort))Method 2: Using find with -newer
# Create reference file
touch /tmp/reference
# ... time passes ...
# Find files newer than reference
find /path/to/dir -type f -newer /tmp/referenceMethod 3: Store and compare file lists
# Initial
ls /path/to/dir > /tmp/before.txt
# ... time passes ...
# Compare
ls /path/to/dir > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txtMethod 4: Using comm (most reliable)
#!/bin/bash
DIR="/path/to/monitor"
prev_files=$(mktemp)
curr_files=$(mktemp)
# Initial snapshot
find "$DIR" -type f | sort > "$prev_files"
while true; do
sleep 5
# Current snapshot
find "$DIR" -type f | sort > "$curr_files"
# Find new files (in curr but not in prev)
new=$(comm -13 "$prev_files" "$curr_files")
if [ -n "$new" ]; then
echo "New files:"
echo "$new"
fi
# Update
cp "$curr_files" "$prev_files"
doneWhat's the practical difference? What do they change?
set -v (Verbose):
- Shows: Commands as written in script
- When: Before execution
- Use: Understanding script flow and logic
Example:
#!/bin/bash
set -v
name="Alice"
echo "Hello, $name"Output:
name="Alice"
echo "Hello, $name"
Hello, Alice
set -x (Execution trace):
- Shows: Commands with variable expansion
- When: Before execution
- Use: Debugging variable values and command results
Example:
#!/bin/bash
set -x
name="Alice"
echo "Hello, $name"Output:
+ name=Alice
+ echo 'Hello, Alice'
Hello, Alice
Side-by-side comparison:
#!/bin/bash
count=5
files="file1.txt file2.txt"
echo "=== With set -v ==="
set -v
for file in $files; do
echo "Processing: $file"
done
set +v
echo ""
echo "=== With set -x ==="
set -x
for file in $files; do
echo "Processing: $file"
done
set +xOutput:
=== With set -v ===
for file in $files; do
echo "Processing: $file"
done
Processing: file1.txt
Processing: file2.txt
=== With set -x ===
+ for file in $files
+ echo 'Processing: file1.txt'
Processing: file1.txt
+ for file in $files
+ echo 'Processing: file2.txt'
Processing: file2.txt
Summary table:
| Feature | set -v | set -x |
|---|---|---|
| Shows variables | No (literal) | Yes (expanded) |
| Shows iteration | Shows loop construct | Shows each iteration |
| Prefix | None | + symbol |
| Best for | Understanding structure | Debugging values |
| Noise level | Lower | Higher |
When to use:
- set -v: Script logic unclear, want to see flow
- set -x: Variables have wrong values, need to see substitution
- SSH keys enable secure, password-less authentication
- Background processing with
&andwaitenables parallelism - trap catches signals for cleanup and custom behavior
- SIGINT/SIGTERM can be ignored for critical operations
- SIGUSR1/SIGUSR2 provide custom signal handlers
- bc enables floating-point arithmetic
- set -v shows literal commands, set -x shows expanded commands
- mktemp creates secure temporary files
- Parallel processing speeds up bulk operations significantly
- Signal handling makes scripts robust and controllable
Advanced BASH scripting enables professional-grade automation!