Blog

  • Master Positional Parameters in Shell Scripting : Mastering $1, $2, $#, $@ (2026)

    What Are Positional Parameters in Shell Scripting?

    Positional Parameters : Bash or shell scripting, positional parameters allow you to access arguments passed to a script or function. They are incredibly useful when you want to handle user inputs dynamically.

    Why Learn Positional Parameters?

    • 📦 Helps you write flexible scripts
    • 🧑‍💻 Makes your scripts accept inputs
    • 🔁 Supports automation and reusability
    • 🧩 Essential for system scripting and DevOps

    Key Positional Parameters Explained

    ParameterMeaning
    $1, $2, …First, second, etc., arguments
    $0Name of the script
    $#Total number of arguments
    $@All arguments as separate words
    $*All arguments as a single word
    "$@"Preserves spaces and treats arguments individually
    "$*"Merges all arguments into one string

    Basic Script Example

    #!/bin/bash
    
    echo "Script Name: $0"
    echo "First Argument: $1"
    echo "Second Argument: $2"
    echo "Total Arguments: $#"
    echo "All Arguments (separately): $@"
    echo "All Arguments (together): $*"

    Run the script like this:

    bash myscript.sh apple banana cherry
    

    Output:

    Script Name: myscript.sh
    First Argument: apple
    Second Argument: banana
    Total Arguments: 3
    All Arguments (separately): apple banana cherry
    All Arguments (together): apple banana cherry
    

    Using "${@}" vs "${*}"

    Let’s see the difference:

    Example Script:

    #!/bin/bash
    
    echo "Using \"\$@\":"
    for arg in "$@"; do
        echo "$arg"
    done
    
    echo "Using \"\$*\":"
    for arg in "$*"; do
        echo "$arg"
    done
    

    Run with:

    bash myscript.sh "hello world" "foo bar"
    

    Output:

    Using "$@":
    hello world
    foo bar
    
    Using "$*":
    hello
    world
    foo
    bar
    

    📌 "$@" keeps the full argument structure. "$*" splits everything.

    Bonus: Accessing All Arguments with a Loop

    #!/bin/bash
    
    echo "You passed $# arguments."
    
    i=1
    for arg in "$@"; do
        echo "Argument $i: $arg"
        ((i++))
    done
    

    Best Practices

    • Always quote your parameters: "$1", "$@"
    • Use $# to validate inputs
    • Use loops with "$@" for safe iteration

    Real-world Use Case: Validate Arguments

    #!/bin/bash
    
    if [ "$#" -ne 2 ]; then
        echo "Usage: $0 <filename> <username>"
        exit 1
    fi
    
    echo "Creating file $1 for user $2..."
    

    5 Practical Examples Using Positional Parameters in Shell Scripts

    Example 1: Greeting Script with $1 and $2

    #!/bin/bash
    
    echo "Hello, $1!"
    echo "Welcome to $2."
    

    Run:

    bash greet.sh Nish India
    

    Output:

    Hello, Nish!
    Welcome to India.
    

    Example 2: Check If Arguments Are Passed Using $#

    #!/bin/bash
    
    if [ "$#" -lt 2 ]; then
      echo "❌ Error: At least 2 arguments required."
      echo "Usage: $0 <arg1> <arg2>"
      exit 1
    fi
    
    echo "You passed $#: $1 and $2"
    

    Run:

    bash check.sh first

    Output:

    ❌ Error: At least 2 arguments required.
    Usage: check.sh <arg1> <arg2>

    Example 3: Loop Through All Arguments Using $@

    #!/bin/bash
    
    echo "📦 List of arguments:"
    for item in "$@"; do
      echo "- $item"
    done

    Run:

    bash list.sh Mango Banana Apple

    Output:

    📦 List of arguments:
    - Mango
    - Banana
    - Apple

    Example 4: Sum Two Numbers Using Positional Parameters

    #!/bin/bash
    
    num1=$1
    num2=$2
    sum=$((num1 + num2))
    
    echo "The sum of $num1 and $num2 is: $sum"

    Run:

    bash sum.sh 5 15

    Output:

    The sum of 5 and 15 is: 20

    Example 5: Handle All Arguments as One String Using $*

    #!/bin/bash
    
    echo "🧵 All arguments as one string: $*"

    Run:

    bash all.sh "one two" "three four"

    Output:

    All arguments as one string: one two three four

    Summary Table

    SymbolWhat It DoesExample
    $1First argumentecho $1
    $2Second argumentecho $2
    $#Argument countecho $#
    $@All arguments (safe loop)for i in "$@"
    $*All arguments (single string)echo $*

    Conclusion

    Mastering positional parameters in shell scripting is essential for writing interactive, powerful, and reusable scripts. Always remember:

    • $1, $2, etc. — access specific inputs
    • $# — count of arguments
    • "$@" — iterate safely
    • Use "$*" when combining arguments into a single string

    Frequently Asked Questions (FAQ)

    Q1: What are positional parameters in shell scripting?

    Positional parameters are special variables in shell scripts that store command-line arguments passed to the script. For example, $1 is the first argument, $2 is the second, and so on.

    Q2: What does $0 mean in a shell script?

    $0 represents the name of the script itself. It’s often used in usage messages or logs to display which script is running.

    Q3: What is the difference between $@ and $*?

    • $@ treats each argument as a separate string, ideal for loops.
    • $* treats all arguments as one single string, often used in quotes.
      Example:
    for arg in "$@"; do echo "$arg"; done   # Handles each argument correctly
    for arg in "$*"; do echo "$arg"; done   # May split arguments incorrectly
    

    Q4: How do I check how many arguments were passed to the script?

    Use $# to get the number of arguments.

    echo "You passed $# arguments."

    Q5: Can I access more than 9 arguments like $10, $11, etc.?

    Yes, but use braces to avoid ambiguity.

    echo "Tenth argument: ${10}"

    Q6: How can I loop through all arguments in a script?

    for arg in "$@"; do
      echo "Argument: $arg"
    done
    

    This safely loops through all arguments with proper quoting.

    Q7: What happens if an argument is missing in $1, $2, etc.?

    It returns an empty string. Always validate input count using $# before accessing specific arguments.

    Q8: Are positional parameters only available in Bash?

    No, they’re available in all POSIX-compliant shells, including sh, Bash, Zsh, and Ksh.

    Q9: How can I print all arguments including the script name?

    echo "Script: $0"
    echo "Arguments: $@"

    Q10: Can I shift positional parameters inside a script?

    Yes! Use shift to move parameters leftward:

    shift
    echo "Now \$1 is: $1"

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @embedded-prep for contributing to this article on Embedded Prep

  • Master CPP Design Patterns Tutorial for Beginners 2026

    What Are Ccpp Design Patterns?

    CPP Design Patterns : Design patterns are proven solutions to common software design problems. They help developers write reusable, maintainable, and scalable code. Think of them as blueprints that guide how to structure your code in a way that solves recurring challenges.

    Why Learn Design Patterns in C++?

    C++ is widely used for system programming, game development, embedded systems, and real-time applications. Learning design patterns in C++ allows you to:

    • Write cleaner and more modular code
    • Improve collaboration in teams
    • Understand industry-level software design
    • Crack technical interviews easily

    Types of Design Patterns

    Design patterns are mainly divided into 3 categories:

    TypeDescription
    CreationalDeal with object creation
    StructuralFocus on object composition
    BehavioralHandle object interaction and communication

    Examples of Each:

    • Creational: Singleton, Factory Method
    • Structural: Adapter, Decorator
    • Behavioral: Observer, Strategy

    Real-World Analogy

    Imagine you’re building a house:

    • Creational Pattern is like choosing how you build rooms (standard floor plan or customized).
    • Structural Pattern is like how rooms are connected (walls, doors).
    • Behavioral Pattern is like how people in rooms communicate (talking, signaling).

    Tools You Need

    To follow this C++ design pattern tutorial series, you need:

    • A basic understanding of C++ syntax
    • An IDE like Code::Blocks, VS Code, or CLion
    • A compiler like g++

    C++ Design Patterns Table

    CategoryPattern NamePurpose
    CreationalSingletonEnsures a class has only one instance and provides a global access point.
    Factory MethodDefines an interface for creating an object, but lets subclasses alter the type.
    Abstract FactoryProvides an interface to create families of related objects.
    BuilderConstructs a complex object step by step.
    PrototypeCreates new objects by copying an existing one.
    StructuralAdapterConverts one interface into another expected by clients.
    BridgeSeparates abstraction from implementation so they can vary independently.
    CompositeComposes objects into tree structures to represent part-whole hierarchies.
    DecoratorDynamically adds behavior to objects.
    FacadeProvides a unified interface to a set of interfaces in a subsystem.
    FlyweightShares objects to support large numbers efficiently.
    ProxyProvides a surrogate or placeholder for another object.
    BehavioralObserverNotifies all dependent objects of any changes to another object.
    StrategyAllows selecting an algorithm at runtime.
    CommandEncapsulates a request as an object.
    StateAllows an object to change its behavior when its internal state changes.
    Template MethodDefines the program skeleton, letting subclasses fill in the steps.
    IteratorProvides a way to access elements of a collection sequentially.
    MediatorDefines an object that coordinates interaction between other objects.
    MementoCaptures and restores an object’s internal state.
    VisitorAdds operations to classes without modifying them.
    InterpreterInterprets sentences in a language using a defined grammar.
    Chain of ResponsibilityPasses a request through a chain of handlers.


    C++ Adding Two Numbers

    #include <iostream>
    
    class CalculatorSingleton {
    private:
        // Private constructor to prevent instantiation
        CalculatorSingleton() {}
    
        // Delete copy constructor and assignment operator
        CalculatorSingleton(const CalculatorSingleton&) = delete;
        CalculatorSingleton& operator=(const CalculatorSingleton&) = delete;
    
    public:
        // Public method to get the single instance
        static CalculatorSingleton& getInstance() {
            static CalculatorSingleton instance;
            return instance;
        }
    
        // Method to add two numbers
        int add(int a, int b) {
            return a + b;
        }
    };
    
    int main() {
        // Get the singleton instance
        CalculatorSingleton& calc = CalculatorSingleton::getInstance();
    
        // Add two numbers
        int result = calc.add(10, 20);
    
        // Print the result
        std::cout << "The sum is: " << result << std::endl;
    
        return 0;
    }
    

    🔍 Output:

    The sum is: 30

    Frequently Asked Questions (FAQs)

    What are design patterns in C++?

    Design patterns in C++ are reusable solutions to common software design problems. They represent best practices used by experienced developers to solve recurring problems in software architecture and object-oriented design.

    Why should I learn design patterns in C++?

    Learning design patterns helps you:

    • Write clean, reusable, and maintainable code
    • Understand and apply industry-standard practices
    • Improve problem-solving and architecture design skills
    • Prepare for technical interviews and real-world development

    Is this tutorial suitable for complete beginners?

    Yes! This tutorial is specially designed for beginners. It explains design patterns in simple language, with easy-to-understand examples and real-world use cases in C++.

    What topics will be covered in this C++ design patterns series?

    The tutorial will cover:

    • Introduction to Design Patterns
    • Types of Design Patterns (Creational, Structural, Behavioral)
    • Step-by-step implementation of each pattern in C++
    • Best practices and use cases

    Do I need to be an expert in C++ to follow this tutorial?

    No. You just need to have a basic understanding of C++, such as classes, objects, inheritance, and functions. The tutorial is crafted for beginners and will guide you through each concept.

    Are there code examples and explanations for each pattern?

    Yes! Every design pattern in this series comes with:

    • Simple code examples in C++
    • Detailed explanations
    • Diagrams (where needed)
    • Real-world applications

    Will this help in job interviews?

    Absolutely. Many software engineering interviews include questions on design patterns. Understanding these concepts will give you a competitive edge during technical interviews.

    How can I practice these design patterns?

    Each tutorial includes:

    • Hands-on exercises
    • Code snippets you can run and modify
    • Mini-project ideas to build confidence

    Is this Master CPP Design tutorials free?

    Yes, the entire Cpp Design Patterns Tutorial for Beginners is free to read and follow. No sign-up or payment is required.

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • O – Open/Closed Principle (OCP) | Master CPP Design Patterns 2026

    The Open/Closed Principle (OCP) is a golden rule of clean software design that says:

    “Software entities should be open for extension but closed for modification.”

    This means once your class or module is working correctly, you shouldn’t need to change its core logic when requirements evolve—you should extend it instead.

    In this tutorial, we’ll explore how C++ developers can apply OCP using abstraction, inheritance, and polymorphism. You’ll learn how to write classes that adapt to new behavior without breaking or modifying existing code, making your software more maintainable, scalable, and bug-resistant.

    What you’ll learn:

    • Real-world scenarios where OCP saves your codebase
    • How to refactor tightly coupled classes into OCP-compliant ones
    • OCP in action using abstract base classes and virtual functions
    • Clean, modern C++ examples explained line-by-line

    Perfect for senior engineers and beginners looking to strengthen their design pattern foundation in 2025-ready C++ code!

    What is OCP?

    Definition:

    Software entities (like classes, modules, functions) should be open for extension, but closed for modification.

    What does that mean in simple terms?

    • Open for extension ➜ You should be able to add new features or behaviors to a class/module.
    • Closed for modification ➜ You should not change the existing code of that class/module when adding new features.

    Why does this matter?

    Imagine you’ve released a class into production. If you keep modifying it every time there’s a new requirement:

    • You might introduce bugs in already tested code.
    • You’ll have to retest the whole thing.
    • It becomes hard to maintain and scale.

    Instead, we use abstraction (like interfaces or base classes) so we can extend behavior by adding new code, not changing existing code.

    Real-World Analogy

    Say you have a power socket.
    Initially, it only supports a fan. Now you want to plug in a charger, light, or heater.

    Bad approach:
    Open the wall and change the wiring every time to support new devices.

    Good approach:
    Use standard sockets and plugs. You don’t change the wall — just plug in a new device.

    That’s the Open/Closed Principle in action.

    Example in C++: Without OCP ❌

    class Invoice {
    public:
        string type;
    
        double calculateTotal() {
            if (type == "Standard") {
                return 100.0;
            } else if (type == "Premium") {
                return 200.0;
            } else {
                return 0.0;
            }
        }
    };
    

    Problem:

    • Every time you want a new invoice type (like “Gold”, “Student”, etc.), you must modify this class.
    • You’re violating OCP because you’re not closed for modification.

    Better Design With OCP

    We use polymorphism (base class + derived classes):

    class Invoice {
    public:
        virtual double calculateTotal() = 0; // abstract method
    };
    

    Now, create new types without modifying the original class:

    class StandardInvoice : public Invoice {
    public:
        double calculateTotal() override {
            return 100.0;
        }
    };
    
    class PremiumInvoice : public Invoice {
    public:
        double calculateTotal() override {
            return 200.0;
        }
    };
    

    Now you can extend it:

    class StudentInvoice : public Invoice {
    public:
        double calculateTotal() override {
            return 50.0;
        }
    };
    

    Your existing code doesn’t change, only new classes are added.

    Testing the Principle

    void printInvoiceTotal(Invoice* inv) {
        cout << "Total: " << inv->calculateTotal() << endl;
    }
    

    This function works for all invoice types — standard, premium, student — without changing anything.

    Benefits of Following OCP

    ✅ Add new features without breaking existing ones
    ✅ Prevent bugs in well-tested code
    ✅ Easier collaboration in teams
    ✅ Better suited for large, evolving projects

    Signs You’re Violating OCP

    • You see a lot of if, switch, or case checking types or conditions.
    • You modify the same class frequently for different behaviors.

    Key Tools to Implement OCP in C++

    • Abstract Base Classes (interfaces) ➜ Define a contract
    • Virtual Functions ➜ Allow extension through overriding
    • Polymorphism ➜ Treat derived classes uniformly
    • Design Patterns like Strategy or Factory ➜ Naturally follow OCP

    Summary

    FeatureGoodBad
    Add new types➕ Add a new class❌ Edit existing code
    Follows OCP
    Code safetySafe and extendableRisky and fragile

    Want Hands-On Practice?

    I can help you:

    • Build a shape area calculator using OCP
    • Implement a payment system that handles different methods (like card, UPI, wallet) using OCP
    • Understand where and when to use virtual functions and interfaces

    Top Interview Questions on Open/Closed Principle (OCP) C++ Focused

    1. What is the Open/Closed Principle, and how is it applied in C++?
    2. Can you explain the benefits of using the Open/Closed Principle in real-world C++ projects?
    3. How does the Open/Closed Principle improve code maintainability and scalability in C++ applications?
    4. What design patterns in C++ support the Open/Closed Principle?
    5. Give a C++ code example that violates the Open/Closed Principle and explain how to refactor it.
    6. How do abstract classes and virtual functions help implement the Open/Closed Principle in C++?
    7. Can the Open/Closed Principle be misused? What are the trade-offs in C++ design?
    8. Describe a time in your project where applying the Open/Closed Principle in C++ saved you from introducing bugs.
    9. What is the relationship between the Open/Closed Principle and the Liskov Substitution Principle in C++?
    10. How do you ensure that your C++ class hierarchy adheres to the Open/Closed Principle during software evolution?
    11. What role do interfaces or pure virtual classes play in implementing the Open/Closed Principle in C++?
    12. How does the use of templates in C++ affect adherence to the Open/Closed Principle?
    13. Why is the Open/Closed Principle considered a core SOLID principle in modern C++ software architecture?
    14. Can you demonstrate how to use the Strategy pattern in C++ to follow the Open/Closed Principle?
    15. How do plugins or dynamic loading in C++ systems relate to the Open/Closed Principle?

    Frequently Asked Questions (FAQ) – Open/Closed Principle (OCP)

    1. What is the Open/Closed Principle in C++?

    The Open/Closed Principle is one of the SOLID principles of object-oriented design. It states that a class, module, or function should be open for extension but closed for modification. This means you can add new features or behaviors without altering existing code, which helps in preventing bugs and promoting reusability.

    2. Why is the Open/Closed Principle important in software design?

    The Open/Closed Principle helps maintain stability in your codebase. By avoiding modifications to tested code and instead extending behavior, your system becomes more resilient to changes, easier to maintain, and better prepared for future enhancements.

    3. How do I implement the Open/Closed Principle in C++?

    You can implement the Open/Closed Principle in C++ using:

    • Abstract base classes or interfaces
    • Virtual functions and inheritance
    • Strategy or Decorator design patterns
      This allows new functionality to be introduced via subclasses without changing existing logic.

    4. What is a real-life example of the Open/Closed Principle?

    Imagine you have a PaymentProcessor class that handles payments. Instead of modifying it every time a new payment method (like UPI or crypto) is added, you create new subclasses like UPIPayment or CryptoPayment that extend the base PaymentProcessor. This follows the Open/Closed Principle by adding functionality without touching old code.

    5. Does following the Open/Closed Principle increase code complexity?

    Initially, applying the Open/Closed Principle may introduce abstraction layers that seem complex. However, in large-scale or growing systems, it pays off by making code more flexible, testable, and modular in the long run.

    6. What are the common mistakes while applying the Open/Closed Principle?

    Some developers over-engineer by applying the Open/Closed Principle too early or unnecessarily. It’s best to apply it when a class is likely to change in the future, not for every trivial use case. Premature abstraction can lead to harder-to-understand code.

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • L – Liskov Substitution Principle (LSP) | Master CPP Design Patterns 2026

    Liskov Substitution Principle (LSP)

    The Liskov Substitution Principle is one of the SOLID principles of object-oriented programming. It was introduced by Barbara Liskov in 1987 and it states:

    “Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.”

    What It Really Means:

    Imagine you’re working with a class called Bird that has a method fly(). If you create a subclass Penguin from Bird, but Penguin can’t fly, then substituting Bird with Penguin could break your program. That’s a violation of LSP.

    The principle pushes us to design subclasses that truly behave like their parent class, so code that uses the base class doesn’t have to worry about the specific subclass being used.

    LSP in Action (Simple Example):

    class Bird {
    public:
        virtual void fly() {
            std::cout << "Flying..." << std::endl;
        }
    };
    
    class Sparrow : public Bird {
    public:
        void fly() override {
            std::cout << "Sparrow flying!" << std::endl;
        }
    };
    
    // This class violates LSP
    class Ostrich : public Bird {
    public:
        void fly() override {
            throw std::logic_error("Ostriches can't fly!");
        }
    };
    

    In the example above, Ostrich breaks LSP because it can’t perform the fly() behavior expected from Bird.

    Why It Matters:

    • Encourages safe inheritance.
    • Makes code more reliable and maintainable.
    • Helps avoid unexpected runtime errors.
    • Ensures that your class hierarchy makes logical sense.

    How to Stick to LSP:

    • Don’t override methods in a way that changes their expected behavior.
    • Use composition over inheritance if subclass behavior doesn’t match the base class.
    • Clearly define contracts (what methods are supposed to do) and ensure subclasses respect them.

    What is LSP?

    Definition by Barbara Liskov:

    If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.

    Simple Version:

    A subclass should be able to stand in for its parent class without breaking the program.

    What does that mean?

    Let’s break it down:

    • You create a base class (Parent class).
    • Then you make a derived class (Child class).
    • The child should behave in a way that’s expected from the parent.
    • You should be able to use the child wherever you used the parent, and things should just work.

    Real-Life Analogy

    Let’s say:

    • You have a class “Bird” with a method fly().
    • You create a subclass “Parrot” — fine, it can fly.
    • Now you create a subclass “Penguin” — 🐧 uh-oh! Penguins can’t fly.

    If you call fly() on a Penguin object assuming it’s a Bird, your program breaks the expectation.

    🚫 That violates LSP!

    Example in C++ that Violates LSP

    class Bird {
    public:
        virtual void fly() {
            cout << "Flying..." << endl;
        }
    };
    
    class Sparrow : public Bird {
    public:
        void fly() override {
            cout << "Sparrow flying" << endl;
        }
    };
    
    class Ostrich : public Bird {
    public:
        void fly() override {
            throw runtime_error("Ostriches can't fly!");
        }
    };
    

    Problem:

    • If someone uses Bird* b = new Ostrich(); b->fly(); expecting it to fly — it crashes.
    • Ostrich is a Bird, but it doesn’t behave like a Bird should.

    This violates LSP.

    LSP-Compliant Design

    Solution: Refactor your classes to respect behavior expectations.

    class Bird {
    public:
        virtual void eat() {
            cout << "Bird eating" << endl;
        }
    };
    
    class FlyingBird : public Bird {
    public:
        virtual void fly() = 0;
    };
    
    class Sparrow : public FlyingBird {
    public:
        void fly() override {
            cout << "Sparrow flying" << endl;
        }
    };
    
    class Ostrich : public Bird {
        // No fly() here
    };
    

    Now:

    • Ostrich doesn’t pretend to be a flying bird.
    • If your logic requires only flying birds, you use the FlyingBird type.
    • LSP is safely respected.

    LSP in Code: A Simple, Clear Example

    class Rectangle {
    public:
        virtual void setWidth(int w) { width = w; }
        virtual void setHeight(int h) { height = h; }
        virtual int getArea() { return width * height; }
    
    protected:
        int width;
        int height;
    };
    

    Now we make a Square from Rectangle:

    class Square : public Rectangle {
    public:
        void setWidth(int w) override {
            width = height = w;
        }
    
        void setHeight(int h) override {
            width = height = h;
        }
    };
    

    Problem:

    If someone does this:

    Rectangle* r = new Square();
    r->setWidth(5);
    r->setHeight(10);
    cout << r->getArea();  // Expected: 50, But it gives 100
    

    Uh-oh! Because setting width or height sets both in Square.
    This breaks expected behavior.
    It violates LSP.

    LSP-Compliant Refactor

    Instead, don’t inherit Square from Rectangle directly if they behave differently.

    Maybe use composition or separate hierarchy.

    LSP Summary Table

    ConceptBad (Violates LSP)Good (Follows LSP)
    Penguin inherits Bird with fly()Penguins can’t flySeparate class for non-flying birds
    Square inherits RectangleArea breaks expectationsDesign Square separately
    Subclass breaks parent’s ruleThrows, skips behaviorBehaves as parent promises

    Key Tips to Follow LSP

    • Derived classes must honor contracts/behavior of base class.
    • Don’t override methods in a way that changes expected behavior.
    • Use interfaces or abstract classes to separate capabilities (like flying).
    • Prefer composition over inheritance when behaviors differ.

    Want to Try a LSP-based Mini Project?

    We can build:

    • A PaymentMethod base class (pay())
    • Subclasses: CreditCard, UPI, Wallet
    • And see how breaking or respecting LSP affects real logic

    FAQ on Liskov Substitution Principle (LSP) in C++

    1. What is the Liskov Substitution Principle (LSP) in C++?

    The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented programming. It states that objects of a derived class should be replaceable with objects of their base class without affecting the correctness of the program. In simple terms, subclasses should behave like their parent classes without breaking functionality.

    2. Why is the Liskov Substitution Principle important in software design?

    LSP ensures code reliability, reusability, and maintainability. By following it, developers prevent unexpected behaviors when using inheritance. It reduces bugs, improves abstraction, and makes the system easier to extend in the future.

    3. Can you give a simple C++ example of the Liskov Substitution Principle?

    Yes.

    #include <iostream>
    using namespace std;
    
    class Bird {
    public:
        virtual void fly() { cout << "Bird can fly\n"; }
    };
    
    class Sparrow : public Bird {
    public:
        void fly() override { cout << "Sparrow flying high\n"; }
    };
    
    // Substitution works correctly
    int main() {
        Bird* b = new Sparrow();
        b->fly();  // Works fine as Sparrow behaves like Bird
        delete b;
        return 0;
    }
    

    Here, Sparrow can substitute Bird without breaking the program, which follows LSP.

    4. What happens if LSP is violated in C++?

    Violating LSP leads to runtime errors, unexpected behaviors, and poor code design. For example, if a derived class overrides a base class function in a way that changes its expected behavior, the system may produce wrong results or even crash.

    5. How does the Liskov Substitution Principle relate to C++ design patterns?

    Many C++ design patterns, such as Strategy, Template Method, and Factory Method, rely on LSP to ensure that subclasses can replace base classes seamlessly. Without LSP, these patterns lose their flexibility and correctness.

    6. How can I check if my C++ code follows the Liskov Substitution Principle?

    You can verify by asking:

    • Can I replace every instance of the base class with the subclass?
    • Does the subclass maintain the expected behavior of the parent?
      If the answer is yes, then your code follows LSP.

    7. What are real-life examples of the Liskov Substitution Principle?

    • Vehicles: A Car is a type of Vehicle and can be used wherever a Vehicle is expected.
    • Shapes: A Circle should behave like a Shape without altering the expected behavior of a Shape class.
    • Payment Systems: A CreditCardPayment should work wherever a generic PaymentMethod is required.

    8. How does LSP improve C++ code quality?

    • Promotes polymorphism
    • Reduces tight coupling
    • Makes code testable and scalable
    • Encourages clean architecture with fewer bugs

    9. What is the difference between LSP and the Open-Closed Principle (OCP)?

    • LSP ensures that subclasses can replace base classes without breaking functionality.
    • OCP ensures that classes are open for extension but closed for modification.
      Both are connected — violating LSP often means violating OCP as well.

    10. How is Liskov Substitution Principle used in modern C++ projects in 2025?

    In 2025 C++ development, LSP is applied in:

    • Embedded systems for safe hardware abstraction
    • Game development for character and object hierarchies
    • Enterprise applications for scalable and maintainable architectures
    • Design patterns to ensure flexible and reusable solutions

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • Single Responsibility Principle (SRP) | Master CPP Design Patterns 2026

    When writing clean and maintainable code, the Single Responsibility Principle (SRP) is one of the most important design principles to follow. It is the first principle in the SOLID design principles, which are a set of five rules that help developers write better object-oriented code.

    In this article, you’ll learn:

    • What the Single Responsibility Principle is
    • Why it matters
    • Real-world and code examples
    • Benefits of using SRP
    • Best practices to follow

    What is the Single Responsibility Principle?

    The Single Responsibility Principle (SRP) states:

    A class should have only one reason to change.

    This means that a class should do only one thing and should have only one responsibility. In other words, it should only focus on one task or job.

    If a class is doing multiple unrelated tasks, then it violates SRP and becomes harder to understand, test, and maintain.

    Real-World Analogy

    Imagine you own a house and hire a cleaner, a plumber, and an electrician. Each one has a specific responsibility:

    • The cleaner cleans the house.
    • The plumber fixes water leaks.
    • The electrician repairs electric faults.

    Now, imagine one person doing all three jobs. If anything goes wrong, it’s harder to pinpoint the issue or fix just one part without affecting the others.

    The same concept applies in programming.

    Code Example Without SRP (Bad Practice)

    class Report {
    public:
        void generateReport() {
            // Logic to generate the report
        }
    
        void printReport() {
            // Logic to print the report
        }
    
        void saveToFile() {
            // Logic to save the report to a file
        }
    };
    

    What’s wrong here?

    This class is doing too much:

    1. Creating a report
    2. Printing the report
    3. Saving the report

    These are different responsibilities, and each one might change for a different reason.

    Code Example With SRP (Good Practice)

    class ReportContent {
    public:
        void generateReport() {
            // Only generates the report content
        }
    };
    
    class ReportPrinter {
    public:
        void printReport() {
            // Only responsible for printing
        }
    };
    
    class ReportSaver {
    public:
        void saveToFile() {
            // Only responsible for saving
        }
    };
    

    Now, each class does only one job, and if anything changes, you only need to modify that specific class. This makes the code clean, testable, and easy to maintain.

    Why is SRP Important?

    • Improves readability – Each class has a clear and focused purpose.
    • Simplifies testing – You can test each class independently.
    • Reduces bugs – Changes in one class don’t affect others unexpectedly.
    • Supports reusability – Classes with a single purpose are easier to reuse.
    • Enhances maintainability – Easier to fix issues or update code later.

    How to Identify SRP Violations

    Ask yourself:

    • Does the class do more than one thing?
    • Can I split this into smaller, more focused classes?
    • Are there multiple reasons this class might change?

    If the answer is yes, then it’s time to refactor your code using SRP.

    Best Practices to Follow

    • Focus each class on one and only one responsibility.
    • Write unit tests to check if a class is tightly focused.
    • Organize files and folders based on responsibilities.
    • Keep your methods short and clear.
    • Refactor often when a class starts growing too large.

    Conclusion

    The Single Responsibility Principle is all about keeping your code clean, organized, and focused. By giving each class a single job, you make your codebase easier to understand, test, and maintain — especially as your project grows.

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • D – Dependency Inversion Principle (DIP) | Master CPP Design Patterns 2026

    Definition (Simple Terms)

    Dependency Inversion Principle : “High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.”

    Let’s Break It Down

    This sounds complex, right? Let’s simplify.

    Imagine you’re building a robot. The brain of the robot is the high-level module — it decides what to do. The legs or arms are low-level modules — they do the actual work.

    Now, if the robot’s brain is tightly connected (depends directly) to the specific type of leg motors, then anytime you change the leg motors, you also have to change the brain’s code. That’s bad.

    DIP says: “Don’t connect the brain directly to the legs. Let both talk through a common language (interface or abstract class).”

    That way, if you ever replace the legs with wheels, the brain doesn’t need to know — it just sends the same instructions.

    Goal of DIP

    • Reduce tight coupling between classes/modules.
    • Increase flexibility, maintainability, and testability of code.
    • Make it easier to swap or change parts of the system.

    Traditional (Wrong) Design Without DIP

    class Keyboard {
    public:
        void input() {
            std::cout << "Keyboard input received\n";
        }
    };
    
    class Computer {
        Keyboard keyboard;  // Direct dependency
    public:
        void getInput() {
            keyboard.input();
        }
    };
    

    What’s wrong?

    • Computer is tightly bound to Keyboard.
    • If you want to replace Keyboard with Touchscreen, you have to change the Computer class.

    DIP Applied (Right Way)

    Step 1: Create an abstraction (interface)

    class IInputDevice {
    public:
        virtual void input() = 0; // Pure virtual function
        virtual ~IInputDevice() = default;
    };
    

    Step 2: Implement the abstraction in concrete classes

    class Keyboard : public IInputDevice {
    public:
        void input() override {
            std::cout << "Keyboard input received\n";
        }
    };
    
    class Touchscreen : public IInputDevice {
    public:
        void input() override {
            std::cout << "Touchscreen input received\n";
        }
    };
    

    Step 3: Depend on the abstraction

    class Computer {
        IInputDevice* inputDevice; // Depends on abstraction
    public:
        Computer(IInputDevice* device) : inputDevice(device) {}
    
        void getInput() {
            inputDevice->input();  // Calls via interface
        }
    };
    

    Step 4: Use it flexibly

    int main() {
        Keyboard kb;
        Touchscreen ts;
    
        Computer comp1(&kb);  // Works with keyboard
        Computer comp2(&ts);  // Works with touchscreen
    
        comp1.getInput();  // Output: Keyboard input received
        comp2.getInput();  // Output: Touchscreen input received
    
        return 0;
    }
    

    Key Takeaways

    ConceptExplanation
    High-Level ModuleThe main controller (e.g., Computer)
    Low-Level ModuleThe working components (e.g., Keyboard, Touchscreen)
    AbstractionA shared interface both modules depend on (e.g., IInputDevice)
    Dependency InversionThe direction of dependency is flipped — both rely on abstraction

    Benefits of Using DIP

    • Easy to switch or add new modules (like adding a mouse or joystick).
    • Improved testability (you can inject mock devices for testing).
    • Better code organization and readability.
    • Promotes Open/Closed Principle (open to extension, closed to modification).

    Real-World Analogy

    Think of a power plug. Your phone charger (low-level device) and your home socket (high-level supply) both depend on a standard plug shape (interface). If tomorrow you buy a new charger, as long as it supports the same plug, you don’t need to rewire your home.

    DIP and Unit Testing

    Since you depend on interfaces, you can write mock versions during testing:

    class MockInputDevice : public IInputDevice {
    public:
        void input() override {
            std::cout << "Mock input for testing\n";
        }
    };
    

    Final Thoughts

    • DIP doesn’t mean no dependencies. It means depend on abstractions rather than concrete implementations.
    • It’s not about eliminating dependencies, but inverting the direction of dependency to favor abstraction.

    Full Comparison of SOLID Principles

    PrincipleFull FormCore IdeaHigh-Level PurposeReal-World AnalogyCode Example HintBenefitsViolating It Leads To
    SSingle Responsibility PrincipleA class should have only one reason to changeBreak down big classes into smaller ones, each doing one jobA chef shouldn’t also be the waiter, cashier, and cleanerSplit Invoice and InvoicePrinterMaintainable, modular codeHard to test, change, and reuse
    OOpen/Closed PrincipleSoftware entities should be open for extension, but closed for modificationAdd new functionality without changing existing codeAdding new plugins to a browser without editing its coreUse inheritance or strategy patternFlexible and extendable codeRisk of breaking existing functionality
    LLiskov Substitution PrincipleSubclasses should be replaceable for their parent classes without altering behaviorDesign classes such that any subclass can be used safely in place of its baseA square should behave like a rectangle if inherited from itAvoid incorrect inheritance like Bird -> Penguin flyingPolymorphic behavior works as expectedUnexpected behavior and bugs
    IInterface Segregation PrincipleClients shouldn’t be forced to depend on interfaces they don’t useBreak big interfaces into smaller, specific onesDon’t give a remote with 50 buttons to someone who only wants to change the volumeSplit IMultifunctionPrinter into IPrint, IScanMinimal and focused contractsConfusing, bloated interfaces
    DDependency Inversion PrincipleHigh-level modules should not depend on low-level modules, both should depend on abstractionsUse interfaces or abstract classes to decouple componentsA plug point shouldn’t care which brand charger you useInject dependencies via constructor or interfaceFlexible, testable, modular architectureTight coupling, hard to replace components

    Detailed Descriptions Per Column

    Core Idea

    • S: One job per class.
    • O: Add, don’t change.
    • L: Substitutable behavior.
    • I: Small, focused interfaces.
    • D: Depend on interfaces, not concrete classes.

    Real-World Analogy

    • Each principle is inspired by common-sense organization and responsibility. These analogies help you remember and visualize them better.

    Code Hint

    • Gives a small direction of what to implement or avoid in code for each principle.

    Benefits

    • Each principle improves maintainability, testability, and flexibility in different ways. Together, they lead to a clean and scalable architecture.

    Violations Lead To

    • Points out what goes wrong when the principle is not followed — like tight coupling, difficult changes, or confusing code.

    How They Work Together (Flow Summary)

    1. SRP gives each class one purpose.
    2. OCP lets you grow your app by adding features instead of modifying old code.
    3. LSP ensures that your new classes don’t break the old ones.
    4. ISP keeps your classes from knowing too much they don’t care about.
    5. DIP connects your parts flexibly through abstraction.

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • Master Function in Shell Scripting(2026)

    Function in Shell Scripting : Shell scripting is a powerful tool used for automating tasks in Unix-like operating systems. For beginners, organizing your shell scripts into reusable functions and using return values can greatly improve the efficiency and maintainability of your code. This beginner-friendly guide will explain how to organize shell scripts using functions, return values, and other best practices to keep your scripts clean, efficient, and easy to understand.

    What are Functions in Shell Scripting?

    In shell scripting, a function is a reusable block of code that performs a specific task. Functions help break down complex scripts into smaller, more manageable parts. Instead of repeating code multiple times, you can define a function once and call it wherever needed.

    Why Use Functions in Shell Scripts?

    1. Reusability: Functions allow you to reuse code without duplication, saving time and reducing errors.
    2. Readability: Dividing your script into smaller functions makes it easier to understand and maintain.
    3. Organization: Functions help you group related tasks together, making your scripts more modular.

    Defining a Function in Shell Scripting

    Here’s how to define and use a simple function in a shell script:

    #!/bin/bash
    
    # Function definition
    greet_user() {
        echo "Hello, $1!"
    }
    
    # Function call
    greet_user "Alice"
    greet_user "Bob"
    

    Explanation:

    • greet_user is a function that takes one argument ($1) and prints a greeting message.
    • The function is called twice in the script: first with the argument “Alice” and then with “Bob”.
    • $1 refers to the first argument passed to the function.

    What are Return Values in Shell Scripting?

    In shell scripting, functions can return values using the return keyword, but it’s important to note that the return statement in shell scripting only allows the function to return an exit status (a number between 0 and 255). This is typically used to indicate whether a function has succeeded or failed.

    To actually return values like strings or numbers, you can use echo or store values in variables.

    Using return for Exit Status

    #!/bin/bash
    
    # Function that checks if a file exists
    check_file() {
        if [[ -f "$1" ]]; then
            return 0  # Success
        else
            return 1  # Failure
        fi
    }
    
    # Calling the function and checking the return status
    check_file "/path/to/file"
    if [[ $? -eq 0 ]]; then
        echo "File exists."
    else
        echo "File does not exist."
    fi
    

    Explanation:

    • The check_file function returns 0 (success) if the file exists and 1 (failure) if it does not.
    • $? checks the exit status of the last command or function, which is used to determine if the file exists.

    Returning Values with echo

    #!/bin/bash
    
    # Function that adds two numbers and returns the result
    add_numbers() {
        local sum=$(( $1 + $2 ))
        echo $sum  # Return the sum
    }
    
    # Calling the function and capturing the result
    result=$(add_numbers 3 5)
    echo "The sum is: $result"
    

    Explanation:

    • add_numbers takes two arguments, adds them together, and uses echo to return the result.
    • The result is captured using command substitution ($(...)) and stored in the result variable.

    Organizing Your Shell Scripts with Functions and Return Values

    Now that we understand how to define functions and use return values in shell scripting, let’s look at how to organize your script with reusable functions to perform multiple tasks.

    Example: Backup Script

    Let’s create a script that backs up files, checks if the backup was successful, and reports the result.

    #!/bin/bash
    
    # Function to backup files
    backup_files() {
        cp -r "$1" "$2"
        if [[ $? -eq 0 ]]; then
            return 0  # Backup successful
        else
            return 1  # Backup failed
        fi
    }
    
    # Function to display status message
    display_status() {
        if [[ $1 -eq 0 ]]; then
            echo "Backup successful!"
        else
            echo "Backup failed!"
        fi
    }
    
    # Main script logic
    source_dir="/path/to/source"
    backup_dir="/path/to/backup"
    
    backup_files "$source_dir" "$backup_dir"
    display_status $?
    

    Key Points:

    1. Modular Functions:
      • backup_files handles the task of copying files and returns a success or failure status.
      • display_status takes the return value and prints the corresponding status message.
    2. Return Values and Exit Status:
      • The return value of backup_files is checked using $? in the display_status function.
    3. Readable and Maintainable: By breaking the script into functions, it becomes easy to understand and maintain. If you need to modify how backups are handled, you only need to update the backup_files function.

    Best Practices for Organizing Shell Scripts

    1. Descriptive Function Names: Always choose function names that clearly describe what the function does. For example, use backup_files rather than a vague name like do_task.
    2. Limit Global Variables: Try to limit the use of global variables. Pass data to functions through parameters and return values instead of relying on global state.
    3. Use Comments: Add comments to your functions to explain their purpose, parameters, and return values. This makes your script easier to understand for others (and yourself) when you revisit it later.
    4. Error Handling: Use return values to handle errors and ensure your script behaves as expected even when something goes wrong. For example, return a non-zero status code if an error occurs, and check that code before continuing.
    5. Reusability: Keep functions general and reusable. Avoid hard-coding specific values within functions—use parameters instead.

    Example 1: File Backup Script

    This example demonstrates a script that backs up files and checks if the backup was successful. It uses functions to make the code more organized.

    #!/bin/bash
    
    # Function to backup files
    backup_files() {
        cp -r "$1" "$2"  # Copy files from source to destination
        if [[ $? -eq 0 ]]; then
            return 0  # Success
        else
            return 1  # Failure
        fi
    }
    
    # Function to display status message
    display_status() {
        if [[ $1 -eq 0 ]]; then
            echo "Backup successful!"
        else
            echo "Backup failed!"
        fi
    }
    
    # Main script logic
    source_dir="/path/to/source"
    backup_dir="/path/to/backup"
    
    backup_files "$source_dir" "$backup_dir"
    display_status $?
    

    Explanation:

    • backup_files function: Copies files from the source directory to the backup directory. It checks if the operation was successful and returns 0 if it was, or 1 if it failed.
    • display_status function: Takes the return value of the backup_files function and prints either a success or failure message.
    • Usage: You run the script by specifying the source and destination directories. The script will handle the backup and display the result.

    Example 2: Calculator Script

    This example shows how to create a simple calculator with functions for addition and subtraction.

    #!/bin/bash
    
    # Function to add two numbers
    add_numbers() {
        local sum=$(( $1 + $2 ))
        echo $sum  # Return the sum
    }
    
    # Function to subtract two numbers
    subtract_numbers() {
        local diff=$(( $1 - $2 ))
        echo $diff  # Return the difference
    }
    
    # Main script logic
    num1=10
    num2=5
    
    sum=$(add_numbers $num1 $num2)
    diff=$(subtract_numbers $num1 $num2)
    
    echo "Sum: $sum"
    echo "Difference: $diff"
    

    Explanation:

    • add_numbers function: Takes two arguments, adds them together, and returns the result using echo.
    • subtract_numbers function: Takes two arguments, subtracts them, and returns the result using echo.
    • Usage: This script calculates the sum and difference of num1 and num2, and displays the results.

    Example 3: Directory Check Script

    This example checks whether a directory exists, and prints a message accordingly. It uses a function to do the checking.

    #!/bin/bash
    
    # Function to check if a directory exists
    check_directory() {
        if [[ -d "$1" ]]; then
            return 0  # Directory exists
        else
            return 1  # Directory does not exist
        fi
    }
    
    # Main script logic
    dir="/path/to/directory"
    
    check_directory "$dir"
    if [[ $? -eq 0 ]]; then
        echo "Directory exists."
    else
        echo "Directory does not exist."
    fi
    

    Explanation:

    • check_directory function: Takes a directory path as an argument and checks if it exists using the -d test. It returns 0 if the directory exists and 1 if it does not.
    • Usage: The script checks if the directory specified in the dir variable exists, and displays a message accordingly.

    Key Concepts from the Examples:

    1. Reusable Functions: In each example, the code is organized into functions (backup_files, add_numbers, check_directory) that perform specific tasks. This makes the script easier to maintain and reuse.
    2. Return Values: Functions either return a value (using echo) or return an exit status (using return), which is then used by the main script to take action.
    3. Modularity: Each task (e.g., backup, calculation, directory check) is separated into its own function, making the script more organized and readable.

    Frequently Asked Questions (FAQ) | Function in Shell Scripting

    1. What is the purpose of using functions in shell scripts?

    Answer: Functions in shell scripts are used to modularize code, making it more readable, reusable, and easier to maintain. Instead of writing repetitive code, you can create functions for specific tasks and call them multiple times throughout the script. This reduces redundancy and makes the script more organized.

    2. How do I define a function in a shell script?

    Answer: You define a function in a shell script using the following syntax:

    function_name() {
        # Code block
    }
    

    For example:

    greet_user() {
        echo "Hello, $1!"
    }
    

    You can call the function by using its name followed by any arguments, like this:

    greet_user "Alice"

    3. How do I pass arguments to a function in a shell script?

    Answer: You pass arguments to a function just like you would for a command-line program. Inside the function, you refer to the arguments using $1, $2, etc. (where $1 is the first argument, $2 is the second, and so on).

    Example:

    greet_user() {
        echo "Hello, $1!"
    }
    
    greet_user "Alice"  # Outputs: Hello, Alice!
    

    4. Can I return a value from a shell function?

    Answer: Shell functions do not directly return values like functions in other programming languages. However, you can use echo to output a value or use return to indicate success or failure (using an exit status code).

    • To return a status (success or failure), use the return keyword (with values from 0 to 255): success() { return 0 # Success } failure() { return 1 # Failure }
    • To return actual values (like strings or numbers), use echo: add_numbers() { local sum=$(( $1 + $2 )) echo $sum # Return the sum } result=$(add_numbers 5 10) echo "The result is: $result" # Outputs: The result is: 15

    5. What does the $? symbol do in a shell script?

    Answer: $? is a special variable that holds the exit status of the last executed command or function. A value of 0 indicates success, while any non-zero value indicates an error or failure.

    For example:

    check_directory() {
        if [[ -d "$1" ]]; then
            return 0  # Directory exists
        else
            return 1  # Directory does not exist
        fi
    }
    
    check_directory "/path/to/dir"
    if [[ $? -eq 0 ]]; then
        echo "Directory exists."
    else
        echo "Directory does not exist."
    fi
    

    In this example, $? is used to check if the check_directory function returned 0 (success) or 1 (failure).

    6. How do I handle errors in shell scripts using return values?

    Answer: To handle errors, you can return different exit statuses from your functions and check them in the main script. The most common approach is to use return 0 for success and return 1 (or other non-zero values) for failure.

    Example:

    copy_file() {
        cp "$1" "$2"
        if [[ $? -eq 0 ]]; then
            return 0  # Success
        else
            return 1  # Failure
        fi
    }
    
    copy_file "/path/to/source" "/path/to/destination"
    if [[ $? -eq 0 ]]; then
        echo "File copied successfully."
    else
        echo "Error copying file."
    fi
    

    7. How can I make my shell script more modular and reusable?

    Answer: To make your script more modular, break it into smaller, reusable functions that each perform a specific task. Avoid repeating code. Instead, define a function for each task and call that function when needed.

    For example, instead of writing the code to check if a file exists multiple times, define a function like this:

    check_file_exists() {
        if [[ -f "$1" ]]; then
            echo "File exists."
        else
            echo "File does not exist."
        fi
    }
    
    check_file_exists "/path/to/file"
    

    Now, you can reuse this function anytime you need to check if a file exists.

    8. What is the difference between echo and return in shell functions?

    Answer:

    • echo is used to output values from a function. This can be captured in a variable using command substitution ($(...)), and it can return strings, numbers, or any other data.
    • return is used to indicate the exit status of a function (or command). It returns a value between 0 and 255, where 0 indicates success, and any non-zero value indicates an error.

    Example:

    # Using echo to return values
    get_sum() {
        local sum=$(( $1 + $2 ))
        echo $sum
    }
    
    result=$(get_sum 5 10)
    echo "The sum is: $result"
    
    # Using return for status
    check_success() {
        return 0  # Success
    }
    
    check_success
    if [[ $? -eq 0 ]]; then
        echo "Operation was successful."
    else
        echo "Operation failed."
    fi
    

    9. Can I pass multiple arguments to a function in shell scripting?

    Answer: Yes, you can pass multiple arguments to a function in shell scripting. Each argument is accessed using $1, $2, $3, and so on. You can also use $@ to refer to all arguments passed to the function.

    Example:

    greet_users() {
        for user in "$@"; do
            echo "Hello, $user!"
        done
    }
    
    greet_users "Alice" "Bob" "Charlie"
    

    This will output:

    Hello, Alice!
    Hello, Bob!
    Hello, Charlie!
    

    10. How do I structure a larger shell script using functions?

    Answer: For larger scripts, break your script into separate functions that perform individual tasks. Each function should be responsible for one thing. Then, in the main part of the script, call these functions in the appropriate order.

    Example of structuring a larger script:

    #!/bin/bash
    
    # Function to initialize directories
    initialize_dirs() {
        mkdir -p "$1"
    }
    
    # Function to copy files
    copy_files() {
        cp -r "$1"/* "$2"
    }
    
    # Function to log actions
    log_action() {
        echo "$(date): $1" >> "$2"
    }
    
    # Main script logic
    initialize_dirs "/path/to/backup"
    copy_files "/path/to/source" "/path/to/backup"
    log_action "Backup completed successfully" "/path/to/logfile.log"
    

    This way, each task is modular and can be easily modified without affecting the rest of the script.

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • Master Loop in Shell Script : for, while, and until (Beginner’s Guide 2026)

    Loop in Shell Script : Repeating tasks is a common need in programming, and Bash scripting makes it super simple with loops. Whether you’re a total beginner or just brushing up, this guide will walk you through for, while, and until loops in Bash in a clear and practical way.

    Loop in Shell Script :

    Why Loops Matter in Bash

    Loops let you automate repetitive tasks, saving time and reducing errors. Imagine printing numbers, processing files, or running commands multiple times — loops make it all easy.

    In Bash, there are three main types of loops:

    • for loop – great for iterating over a list or range
    • while loop – runs as long as a condition is true
    • until loop – runs until a condition becomes true

    Let’s explore each with easy examples!

    The for Loop

    The for loop is perfect when you know how many times you want to repeat a task.

    Syntax:

    for variable in list
    do
      # commands
    done
    

    Example 1: Print numbers 1 to 5

    for i in 1 2 3 4 5
    do
      echo "Number: $i"
    done
    

    Example 2: Loop through a list of names

    for name in Alice Bob Charlie
    do
      echo "Hello, $name!"
    done
    

    The while Loop

    The while loop continues as long as a condition is true.

    Syntax:

    while [ condition ]
    do
      # commands
    done
    

    Example: Count from 1 to 5

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

    Tip: Always make sure your condition will eventually become false to avoid infinite loops!

    The until Loop

    The until loop is like the opposite of while. It runs until the condition is true.

    Syntax:

    until [ condition ]
    do
      # commands
    done
    

    Example: Count from 1 to 5 using until

    count=1
    until [ $count -gt 5 ]
    do
      echo "Count: $count"
      ((count++))
    done
    

    Notice the key difference: until continues as long as the condition is false.

    Bash Loop Tips for Beginners

    • Use echo to debug your loop logic.
    • Prefer for loops for simple repetitions and lists.
    • Use while or until for more flexible conditions.
    • Don’t forget the do and done keywords!

    Is do while supported in Bash?

    No, Bash does not support do...while loops directly.

    In C/C++, a do...while loop runs at least once, and checks the condition after executing the block.

    // In C
    do {
        // code
    } while (condition);
    

    Workaround in Bash

    You can simulate a do...while loop in Bash using a while loop with a break condition inside.

    Bash Equivalent (Simulating do...while):

    count=1
    while true
    do
      echo "Count: $count"
      ((count++))
    
      if [ $count -gt 5 ]; then
        break
      fi
    done
    

    Here’s what’s happening:

    • while true ensures the loop runs at least once.
    • We manually check the condition after executing the body.
    • break exits the loop once the condition is met — mimicking do...while.

    Pro Tip

    If you must ensure a loop runs at least once in Bash, this pattern is your go-to replacement for do...while.

    FAQ | Learn How to Repeat Tasks Using for, while, and until Loops in Bash

    1. What is a loop in Bash?

    A loop in Bash is a control structure that allows you to repeat a task or a set of tasks multiple times without needing to write the same code over and over again. Loops are essential for automating repetitive tasks in your Bash scripts.

    2. What’s the difference between for, while, and until loops in Bash?

    • for loop: Executes a block of code for each item in a list or a range. Ideal when you know how many times you want the loop to run.
    • while loop: Runs as long as a given condition is true. Use this when the number of iterations isn’t fixed, but the condition should be true.
    • until loop: Similar to while, but it runs until the condition becomes true, meaning it stops when the condition is true.

    3. When should I use a for loop in Bash?

    Use a for loop when you want to iterate over a list or a range of numbers. It’s perfect for scenarios where you know the exact number of iterations you need.

    Example:

    for i in 1 2 3 4 5
    do
      echo "Iteration: $i"
    done
    

    4. How do while and until loops differ?

    • A while loop checks if a condition is true before executing the code block. It continues as long as the condition remains true.
    • An until loop works oppositely. It checks if a condition is false and will continue executing the block until the condition becomes true.

    Example while:

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

    Example until:

    count=1
    until [ $count -gt 5 ]
    do
      echo "Count: $count"
      ((count++))
    done
    

    5. How can I stop an infinite loop in Bash?

    To prevent an infinite loop, make sure your loop condition will eventually become false. However, if you do find yourself in an infinite loop, you can stop it by pressing Ctrl+C in the terminal.

    6. Can I combine multiple loops in Bash?

    Yes, you can nest loops in Bash. This means you can place one loop inside another. Here’s an example with a for loop inside a while loop:

    count=1
    while [ $count -le 3 ]
    do
      for i in 1 2 3
      do
        echo "Count: $count, Inner loop: $i"
      done
      ((count++))
    done
    

    7. What is the syntax for for, while, and until loops?

    • for loop: for variable in list do # commands done
    • while loop: while [ condition ] do # commands done
    • until loop: until [ condition ] do # commands done

    8. Can I use a for loop with ranges in Bash?

    Yes, you can. Bash supports range iteration in for loops by specifying a range like this:

    for i in {1..5}
    do
      echo "Number: $i"
    done
    

    This will output numbers from 1 to 5.

    9. What happens if I forget the do or done in a loop?

    If you forget do or done, Bash will throw a syntax error. Both do and done are essential to mark the start and end of the code block inside loops.

    10. Can I break out of a loop in Bash?

    Yes! You can use the break command to exit a loop early, regardless of the loop condition.

    Example:

    for i in {1..10}
    do
      if [ $i -eq 5 ]; then
        break
      fi
      echo "Number: $i"
    done
    

    This will print numbers 1 to 4, then exit the loop when $i equals 5.

    11. What is the continue statement used for in loops?

    The continue statement skips the rest of the current iteration and moves to the next iteration of the loop.

    Example:

    for i in {1..5}
    do
      if [ $i -eq 3 ]; then
        continue
      fi
      echo "Number: $i"
    done
    

    This will skip the number 3 and print 1, 2, 4, 5

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @embedded-prep for contributing to this article on Embedded Prep

  • Master Shell script variables 2026

    Shell script variables are named memory locations used to store data that can be referenced and manipulated throughout a script. They allow scripts to be dynamic and flexible by holding values such as text, numbers, or the output of commands. Variables in shell scripts help reduce redundancy, enhance readability, and make automation tasks more efficient. They are defined without data types and can be easily modified during script execution, making them essential tools for controlling flow, configuring behavior, and managing data in Unix-based scripting environments.

    Shell script variables

    User-Defined Variables

    These are variables created by the user in a shell script or directly in the terminal.

    Syntax:

    variable_name=value

    No spaces around =.

    Example:

    name="Nish"
    echo "Hello, $name"

    Important Points:

    • Use $variable_name to access the value.
    • To avoid issues, wrap variables in double quotes ("$var").

    Environment Variables

    These are predefined variables provided by the system or the user for the shell environment.

    Used by the OS and shell to configure behavior.

    Examples:

    • HOME – your home directory
    • PATH – list of directories to search for commands
    • USER – your username
    • SHELL – path to the current shell

    Usage:

    echo $HOME
    echo $PATH

    Exporting a Variable:

    To make a user-defined variable available to child processes:

    export myvar="hello"

    Special Variables

    These are read-only shell variables that give info about the script or shell environment.

    VariableDescription
    $0Name of the script
    $1, $2...$nPositional parameters (arguments passed to the script)
    $#Number of arguments passed
    $@All arguments (as separate words)
    $*All arguments (as a single word)
    $$PID of the current shell
    $?Exit status of the last command
    $!PID of the last background process

    Example:

    #!/bin/bash
    echo "Script name: $0"
    echo "1st argument: $1"
    echo "Number of args: $#"

    Summary:

    TypeWho defines it?ScopeExample
    User-definedYouCurrent shellmyname="Nish"
    EnvironmentSystem/UserChild shells tooexport PATH
    SpecialShell itselfRead-only$0, $#, $?

    Shell Script: demo.sh

    #!/bin/bash
    
    # User-defined variable
    username="Nish"
    
    # Environment variable (export it so child processes can access it)
    export project="EmbeddedSystem"
    
    # Special variables
    echo "Script name        : $0"
    echo "1st argument        : $1"
    echo "2nd argument        : $2"
    echo "Total args passed   : $#"
    echo "All args (\$@)       : $@"
    echo "Current user        : $USER"
    echo "Home directory      : $HOME"
    echo "Custom username     : $username"
    echo "Project name        : $project"
    echo "Script PID          : $$"
    
    # Example of command and checking exit status
    ls /tmp
    echo "Exit status of 'ls' : $?"
    

    How to Run:

    chmod +x demo.sh
    ./demo.sh arg1 arg2

    Output Example:

    Script name        : ./demo.sh
    1st argument        : arg1
    2nd argument        : arg2
    Total args passed   : 2
    All args ($@)       : arg1 arg2
    Current user        : nish
    Home directory      : /home/nish
    Custom username     : Nish
    Project name        : EmbeddedSystem
    Script PID          : 12345
    Exit status of 'ls' : 0

    Frequently Asked Questions (FAQ)

    What is user input in Bash?

    User input in Bash refers to the data entered by a user while a script is running. This allows the script to behave dynamically based on the values provided at runtime.

    How do I take input from a user in a Bash script?

    You can use the read command. Example:

    read -p "Enter your name: " name
    echo "Hello, $name!"
    

    Can I hide user input in Bash (like for passwords)?

    Yes! Use the -s option with read to hide the input:

    read -sp "Enter your password: " password
    

    How do I set a default value if no input is provided?

    You can use parameter expansion:

    read -p "Enter your city [New York]: " city
    city=${city:-New York}
    echo "You chose $city"
    

    Can I read multiple inputs at once?

    Absolutely! Just list multiple variable names after read:

    read -p "Enter your first and last name: " first last
    echo "Welcome, $first $last!"
    

    Is user input available after the script ends?

    No. Bash variables—including those storing input—exist only while the script is running, unless you explicitly save them (e.g., in a file).

    How do I validate or restrict input?

    You can use conditional statements or loops to check input:

    while true; do
      read -p "Enter a number: " num
      [[ "$num" =~ ^[0-9]+$ ]] && break
      echo "Please enter a valid number."
    done
    

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep

  • Bash User Input: Master How To read input in shell (2026)

    Bash User Input : Mastering user input in Bash is a fundamental skill every shell scripter must acquire. In this guide, we’ll walk through the modern and practical way of reading input from users using the read command in Bash, tailored for 2025 and beyond. Whether you’re automating system tasks, building CLI tools, or creating interactive scripts, handling user input effectively is key to writing dynamic and responsive scripts.

    We’ll cover everything from basic single-line inputs to advanced input handling—like silent password inputs, setting default values, and reading multiple variables at once. By the end of this tutorial, you’ll not only understand how to use read, but you’ll also be able to create scripts that interact intelligently with users.

    Designed with beginners in mind and polished with pro-level tricks, this guide ensures your journey from novice to Bash input master is smooth and efficient.

    Bash User Input :

    read Command Basics

    The read command is used to take input from the user.

    Syntax:

    read variable_name

    Example 1: Basic User Input

    #!/bin/bash
    
    echo "Enter your name:"
    read name
    echo "Hello, $name!"
    

    Example 2: Prompt Inline with -p

    #!/bin/bash
    
    read -p "Enter your favorite programming language: " language
    echo "Nice! You like $language."
    

    Example 3: Silent Input (useful for passwords) with -s

    #!/bin/bash
    
    read -sp "Enter your password: " password
    echo
    echo "Password entered."
    

    The input won’t be shown on screen while typing.

    Example 4: Reading Multiple Inputs

    #!/bin/bash
    
    read -p "Enter your first and last name: " fname lname
    echo "First Name: $fname"
    echo "Last Name : $lname"
    

    Example 5: Using Input in Conditions

    #!/bin/bash
    
    read -p "Are you sure you want to continue? (yes/no): " answer
    
    if [[ "$answer" == "yes" ]]; then
        echo "Continuing..."
    else
        echo "Exiting..."
    fi
    

    Summary of Useful read Options:

    OptionDescription
    -pPrompt inline before reading input
    -sSilent input (doesn’t echo input)
    -tTimeout for input in seconds
    -nLimit number of characters to read

    Awesome Nish! Here’s a complete interactive shell script that acts like a simple menu-driven program, taking input from the user and responding accordingly.

    Script: menu.sh

    #!/bin/bash
    
    echo "Welcome to the Interactive Menu Program!"
    echo
    
    while true; do
        echo "========== MENU =========="
        echo "1. Show current date and time"
        echo "2. Show current logged-in user"
        echo "3. List files in current directory"
        echo "4. Show system uptime"
        echo "5. Exit"
        echo "=========================="
        read -p "Enter your choice [1-5]: " choice
    
        case $choice in
            1)
                echo "📅 Date and Time: $(date)"
                ;;
            2)
                echo "👤 Logged-in User: $USER"
                ;;
            3)
                echo "📂 Files in $(pwd):"
                ls
                ;;
            4)
                echo "⏱️ System Uptime:"
                uptime
                ;;
            5)
                echo "Goodbye, $USER!"
                break
                ;;
            *)
                echo "❌ Invalid choice! Please select from 1 to 5."
                ;;
        esac
    
        echo
    done
    

    How to Run:

    1. Save as menu.sh
    2. Make it executable: chmod +x menu.sh
    3. Run it: ./menu.sh

    What it Demonstrates:

    • Taking input using read
    • Using case for control flow
    • Calling system commands (date, ls, uptime)
    • User interaction in a loop
    • Graceful exit

    You can also Visit other tutorials of Embedded Prep 

    Special thanks to @mr-raj for contributing to this article on Embedded Prep