Essential shell tips for Linux enthusiasts

linux, shell, script, bash

saving your time: some tips for linux shell

Index #

Looping: command vs script #

command #

for i in {1..5}; do echo $i; done

count=1; while [ $count -le 5 ]; do echo $count; ((count++)); done

script #

#!/bin/bash

echo "Counting to 5 with a for loop:"
for i in {1..5}; do
    echo $i
done

echo "Counting to 5 with a while loop:"
count=1
while [ $count -le 5 ]; do
    echo $count
    ((count++))
done

Signals #

Some scenarios that make sense to deal with signals..

SignalDescriptionHow to Invoke
SIGINTInterrupts the process. Typically generated by pressing Ctrl+C in the terminal.Ctrl + C or kill -2 <pid>
SIGTERMRequests the graceful termination of the process. Used to terminate processes without forcing them.kill -15 <pid>
SIGHUPIndicates that the terminal connection was lost or that the process needs to reload configurations.kill -1 <pid>
SIGKILLForces the immediate termination of a process. Cannot be ignored by the process.kill -9 <pid>
SIGSTOPPauses the execution of a process (can be resumed later).kill -19 <pid>

SIGINT #

This script creates a temporary file and displays a goodbye message when it receives a SIGINT signal (e.g., when the user presses Ctrl+C).

#!/bin/bash

# Function that will be called when the script receives the SIGINT signal
function exitMsg {
    echo "you sent a signal to end, byby !!"
    # command here
    exit
}

# Sets up the trap to call the cleanup function when the script receives SIGINT
trap exitMsg SIGINT

sleep 5

echo "Creating a temporary file..."
touch /tmp/apolzek || exit 1  # Creates a temporary file or exits with an error

SIGTERM #

This script starts a web server that runs indefinitely and shuts down gracefully when it receives a SIGTERM signal. kill <PID>

#!/bin/bash

# Function called upon receiving SIGTERM
function terminateMsg {
    echo "Received SIGTERM, shutting down the server gracefully..."
    # Here you can add commands to close connections or save the state
    exit
}

# Sets up the trap for SIGTERM
trap terminateMsg SIGTERM

echo "Starting web server..."
while true; do
    echo "Server is running... (PID: $$)"
    sleep 2  # Simulates the server's running time
done

SIGHUP #

This script starts a daemon that runs indefinitely and reloads its configuration when it receives a SIGHUP signal. kill -HUP <PID>

#!/bin/bash

# Function called upon receiving SIGHUP
function reloadMsg {
    echo "Received SIGHUP, reloading configuration..."
    # Here you can add commands to reload the configurations
    # Example: source /etc/mydaemon/config.conf
}

# Sets up the trap for SIGHUP
trap reloadMsg SIGHUP

echo "Starting my daemon..."
while true; do
    echo "Daemon is running... (PID: $$)"
    sleep 5  # Simulates the daemon's running time
done

Background processes #

#!/bin/bash

echo "Starting background processes..."

# Process 1
sleep 3 &  # This simulates a long-running task
pid1=$!  # Get the process ID of process 1

# Process 2
sleep 9 &  # This simulates a shorter task
pid2=$!  # Get the process ID of process 2

# Wait for process 1 to finish and notify
wait $pid1
echo "Process 1 has completed."

# Wait for process 2 to finish and notify
wait $pid2
echo "Process 2 has completed."

echo "All processes have finished."

Debugging #

#!/bin/bash

set -x  # Enable debugging mode

echo "Starting the script..."
echo "Doing something..."
sleep 1
echo "Ending the script."

set +x  # Disable debugging mode

echo "now debugging mode is disable"
echo "did you understand ?"

using pipefail..

#!/bin/bash
set -xeuo pipefail

# Uninitialized variable (throws an error with `set -u`)
echo "Attempting to access an uninitialized variable..."
echo "Variable value: $UNINITIALIZED_VAR"

# This command will never be executed due to the previous error
echo "End of script."

String Manipulation and Substitution #

#!/bin/bash

# Defining an original string
original="Linux is amazing!"

# Converting to uppercase
uppercase=${original^^}
echo "Uppercase: $uppercase"
# Output: Uppercase: LINUX IS AMAZING!

## another way
echo "Linux is amazing!" | tr '[:lower:]' '[:upper:]'
echo "Linux is amazing!" | awk '{ print toupper($0) }'

# Converting to lowercase
lowercase=${original,,}
echo "Lowercase: $lowercase"
# Output: Lowercase: linux is amazing!

# Replacing part of the string
modified=${original//amazing/extravagant}
echo "Substitution: $modified"
# Output: Substitution: Linux is extravagant!

# Extracting a substring
substring=${original:7:9}  # Extracts "is amazing"
echo "Substring: $substring"
# Output: Substring: is amazing

# Checking the length of the string
length=${#original}
echo "Length of the string: $length characters"
# Output: Length of the string: 20 characters

Shell Associative Arrays #

#!/bin/bash

# Declare an associative array
declare -A user_info

# Assign key-value pairs
user_info[name]="Alice"
user_info[email]="alice@example.com"
user_info[role]="Admin"

# Access elements by key
echo "User Name: ${user_info[name]}"
echo "User Email: ${user_info[email]}"
echo "User Role: ${user_info[role]}"

# Looping over keys and values
for key in "${!user_info[@]}"; do
    echo "$key: ${user_info[$key]}"
done

YAML files #

Install yq

sudo pacman -S yq

Create yaml example

apiVersion: v1
kind: ComplexConfig
metadata:
  name: example-config
  labels:
    environment: production
    version: "1.0"
spec:
  services:
    - name: service1
      type: LoadBalancer
      ports:
        - name: http
          port: 80
          targetPort: 8080
        - name: https
          port: 443
          targetPort: 8443
      hosts:
        - host: "service1.example.com"
          ip: "192.168.1.10"
          regions:
            - us-east-1
            - eu-west-1
    - name: service2
      type: ClusterIP
      ports:
        - name: grpc
          port: 50051
          targetPort: 50051
      hosts:
        - host: "service2.example.com"
          ip: "192.168.1.20"
          regions:
            - ap-south-1
            - eu-central-1
  config:
    retries: 3
    timeout: 5000
  logging:
    level: debug
    format: json
    outputs:
      - type: file
        path: "/var/log/app.log"
      - type: stdout

filtering data

yq '.metadata.name' config.yaml
yq '.spec.services[].name' config.yaml
yq '.spec.services[1].type' config.yaml
yq '.spec.services[] | select(.name == "service1") | .ports[] | {port, targetPort}' config.yaml
yq '.spec.services[].hosts[] | {host, ip}' config.yaml
yq '.spec.services[] | select(.name == "service2") | .hosts[].regions' config.yaml
yq '.spec.logging.level' config.yaml

yq -r '.metadata.name' config.yaml
yq -r '.spec.services[].name' config.yaml
yq -r '.spec.services[1].type' config.yaml
yq -r '.spec.services[] | select(.name == "service1") | .ports[] | "\(.port) \(.targetPort)"' config.yaml
yq -r '.spec.services[].hosts[] | "\(.host) \(.ip)"' config.yaml
yq -r '.spec.services[] | select(.name == "service2") | .hosts[].regions[]' config.yaml
yq -r '.spec.logging.level' config.yaml

Check input #

#!/bin/bash
# Check if the argument is a directory.
if [[ ! -d "$1" ]]; then
    echo "Error: $1 is not a directory."
    exit 1
fi

validating file path..

#!/bin/bash

# Check if the user provided a file path as an argument
if [[ -z "$1" ]]; then
    echo "No file path provided. Please enter the file path:"
    read -r file_path
else
    file_path="$1"
fi

# Check if the file exists
if [[ -f "$file_path" ]]; then
    echo "File exists, proceeding with backup."
else
    echo "File does not exist. Please check the path and try again."
    exit 1
fi

shellcheck #

shellcheck is a powerful static analysis tool for shell scripts. It helps identify common issues like syntax errors, unused variables, and unsafe practices. Using shellcheck can save time and prevent bugs in your scripts.

shellcheck example.sh

Output from shellcheck

example.sh:5:7: note: Double quote to prevent globbing and word splitting. [SC2086]
example.sh:7:7: warning: Undefined variable: UNDEFINED_VAR. [SC2154]
example.sh:10:7: error: Missing 'fi' to end 'if' statement. [SC1073]