DevOps.Academy
CoursesCurriculumPricing

    Loading…

    ↑ ↓ move · ↵ open · Ctrl K anywhere

    ◔My learningSign in
    DevOps.Academy

    Free, hands-on DevOps lessons. Made with care in India.

    Learn

    All coursesLinux roadmapCurriculumMy learning

    Academy

    PricingCertificates

    Legal

    PrivacyTermsContact

    devops.rajeev.pro

    Linux & BashLesson 3 of 16
    Your progress · 0%
    On this page
    1. Getting help fast
    2. How the shell finds a command
    3. Variables and the environment
    4. Three streams in, three streams out
    5. Redirects and pipes
    6. sudo and root
    ↖ Course roadmap
    Lesson 3 · Shell basics

    Shell basics: PATH, variables, streams and sudo

    How the shell finds commands, how variables and dotfiles shape every session, where output and errors go, and how to use root power without hurting yourself.

    35 min read · beginner · hands-on

    By the end of this lesson you'll be able to

    0 of 5 complete

    Getting help fast

    You'll never remember every option, and nobody expects you to. You need to find the answer in seconds:

    $ ls --help | less           # short summary of options, built into most commands
    $ man ls                     # the full manual. /word searches, n next match, q quits
    $ man 5 crontab              # section 5: the crontab FILE format, not the command (section 1)
    $ man -k password            # search all manual pages for a word (same as apropos)
    $ type cd                    # what kind of command is this?
    cd is a shell builtin
    
    ls --help | less
    • lslist files
    • |pipe: send output to the next command
    • lessscroll through a file; q quits

    Manual sections you'll meet: 1 commands, 5 file formats (/etc/fstab, crontab), 8 admin commands (useradd, sshd). When a command has no man page, try help (for bash builtins, such as help cd) or command --help.

    How the shell finds a command

    When you type grep error app.log, bash has to decide what grep is. It checks, in order:

    The first match wins. That's why order in $PATH matters.© Diagram: DevOps Academy

    The first match wins. That's why order in $PATH matters.© Diagram: DevOps Academy
    $ echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    $ type grep ls cd
    grep is /usr/bin/grep
    ls is aliased to `ls --color=auto'
    cd is a shell builtin
    $ which python3              # the file PATH would run
    /usr/bin/python3
    $ ./deploy.sh                # not in PATH? Give the path yourself
    
    echo $PATH
    • echoprint text

    cd has to be a builtin: a separate program could only change its own folder, not your shell's.

    "Command not found" means one of three things: a typo, the program isn't installed, or it's installed in a folder that isn't in PATH. Check with type name or ls the folder you expect.

    Variables and the environment

    $ APP_ENV=staging            # a shell variable: this shell only. No spaces around =
    $ echo "$APP_ENV"
    staging
    $ bash -c 'echo "[$APP_ENV]"'  # a child process can't see it
    []
    $ export APP_ENV             # now it's an environment variable, inherited by children
    $ bash -c 'echo "[$APP_ENV]"'
    [staging]
    $ env | grep APP_ENV         # every environment variable
    APP_ENV=staging
    $ unset APP_ENV              # remove it
    $ APP_ENV=prod ./deploy.sh   # set it for ONE command only
    
    echo "$APP_ENV"
    • echoprint text
    env | grep APP_ENV
    • |pipe: send output to the next command
    • grepfind lines that match a pattern

    Every program you start gets a copy of the environment. That's how PATH, HOME, USER, LANG and your app's settings reach it, and why changing a variable in one terminal doesn't change it in another.

    Quoting

    You writeBash does
    echo "$HOME/logs"expands variables: /home/rajeev/logs
    echo '$HOME/logs'nothing: prints $HOME/logs literally
    echo "Today is $(date +%A)"runs the command inside $( ) and inserts its output
    rm "$FILE"always quote variables: a name with a space stays one argument

    Making it permanent: the dotfiles

    Variables, aliases and PATH changes vanish when the shell closes. To keep them, put them in a file bash reads at start-up:

    FileRead byPut here
    ~/.bashrcevery interactive bashaliases, PATH, EDITOR, prompt
    ~/.profile (or ~/.bash_profile)login shells (SSH logins)things for the whole session; on Ubuntu it sources ~/.bashrc
    /etc/profile, /etc/bash.bashrceveryonesystem-wide defaults (admins only)
    $ echo 'alias ll="ls -lah"' >> ~/.bashrc
    $ source ~/.bashrc           # reload now, without opening a new terminal
    
    echo 'alias ll="ls -lah"' >> ~/.bashrc
    • echoprint text
    • >>append output to a file

    History

    history lists what you ran. Ctrl+R searches it as you type (press it again for older matches). !! repeats the last command, so sudo !! re-runs it with sudo.

    Three streams in, three streams out

    Every process starts with three open channels, each with a number:

    Output and errors are separate on purpose: you can save one and still see the other.© Diagram: DevOps Academy

    Output and errors are separate on purpose: you can save one and still see the other.© Diagram: DevOps Academy

    That separation is why ls /etc /nope > out.txt still shows the error on screen: you redirected stream 1, and the error travels on stream 2.

    Redirects and pipes

    $ ls /etc > files.txt             # stdout to a file (overwrites it)
    $ ls /etc >> files.txt            # append instead
    $ ls /etc /nope 2> errors.txt     # only errors to a file
    $ ls /etc /nope > all.txt 2>&1    # both, to the same file (order matters)
    $ ls /etc /nope &> all.txt        # bash shorthand for the same
    $ find / -name "*.conf" 2> /dev/null   # throw errors away
    $ sort < names.txt                # stdin from a file
    $ ps aux | grep nginx             # pipe: output of one becomes input of the next
    $ ./deploy.sh | tee deploy.log    # see output AND save it
    $ find . -name "*.log" | xargs gzip    # turn lines of input into arguments
    
    ls /etc > files.txt
    • lslist files
    • >write output to a file, replacing it
    ls /etc >> files.txt
    • >>append output to a file
    ls /etc /nope > all.txt 2>&1
    • 2>&1send errors to the same place as output
    find / -name "*.conf" 2> /dev/null
    • findsearch for files
    • -namematch the file name (wildcards allowed)
    sort < names.txt
    • sortsort lines
    • <read input from a file
    ps aux | grep nginx
    • pslist processes
    • |pipe: send output to the next command
    • grepfind lines that match a pattern
    ./deploy.sh | tee deploy.log
    • teewrite input to a file and pass it on
    find . -name "*.log" | xargs gzip
    • .this folder

    Green is stdout, red is stderr. Each redirect changes where one of them lands.© Animation: DevOps Academy

    Green is stdout, red is stderr. Each redirect changes where one of them lands.© Animation: DevOps Academy

    2>&1 reads as "send stream 2 wherever stream 1 is going right now". So the file redirect has to come first. /dev/null is a bin that throws away whatever you write to it.

    In production

    Cron jobs and scripts run with nobody watching. Always capture both streams and append: ./backup.sh >> /var/log/backup.log 2>&1. Without 2>&1, the error that explains a failed backup goes nowhere, and you find out the night you need the backup.

    sudo and root

    root (user id 0) can do anything: read every file, kill any process, delete the system. Your normal account can't, and that's protection, not a limitation.

    $ whoami
    rajeev
    $ sudo systemctl restart nginx    # one command as root; asks YOUR password
    $ sudo -l                         # what am I allowed to run with sudo?
    $ sudo -i                         # a root shell. The prompt ends in #. Leave with exit
    $ sudo !!                         # re-run the last command with sudo
    
    whoami
    • whoamiprint your user name
    sudo systemctl restart nginx
    • sudorun the next command as root
    • systemctlcontrol systemd services
    sudo commandsu -
    Asks foryour passwordroot's password
    Scopeone commanda whole root shell
    Logged asyour name, every command"root", nothing more

    Prefer sudo for each command over a long-lived root shell. If you do open one with sudo -i, do the one task and exit.

    sudo doesn't apply to redirects

    sudo echo "x" > /etc/app.conf fails with Permission denied: the > runs in your shell before sudo starts. Use echo "x" | sudo tee /etc/app.conf > /dev/null.

    ⌘
    Mini mission

    Install a tool for yourself, and log it

    You keep running the same health-check commands. Turn them into a script called hello that runs from any folder, without sudo, and keeps a log of every run with output and errors.

    Input: A script in ~/bin, and ~/.bashrcOutput: ~/bin/hello and ~/hello.log
    Reveal one safe solution
    $ mkdir -p ~/bin
    $ cat > ~/bin/hello <<'EOF'
    #!/bin/bash
    echo "$(date '+%F %T') host=$(hostname) user=$USER"
    ls /nope                     # a deliberate error, to prove errors are logged too
    EOF
    $ chmod +x ~/bin/hello
    $ echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
    $ source ~/.bashrc
    $ type hello
    hello is /home/rajeev/bin/hello
    $ cd /tmp && hello >> ~/hello.log 2>&1
    $ cat ~/hello.log
    2026-10-13 10:42:07 host=web-01 user=rajeev
    ls: cannot access '/nope': No such file or directory
    
    mkdir -p ~/bin
    • mkdirmake a directory
    • -pcreate missing parents, no error if it exists
    cat > ~/bin/hello <<'EOF'
    • catprint a file
    • >write output to a file, replacing it
    chmod +x ~/bin/hello
    • chmodchange permissions
    echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
    • echoprint text
    • >>append output to a file
    cd /tmp && hello >> ~/hello.log 2>&1
    • cdchange directory
    • &&run the next command only if this one worked
    • 2>&1send errors to the same place as output

    Putting ~/bin first in PATH means your tools win over system ones with the same name. Use that knowingly: never name a script ls or test.

    Try it yourself

    0 of 6 steps done

    Run each task in your own account or terminal, then tick it off.

    Knowledge check

    Quick check

    Question 1 of 5

    You wrote a script deploy.sh in your current folder. Typing deploy.sh says "command not found". Why?

    Go deeper

    Official reading for this lesson

    • Docsbash(1) manual pageUbuntu manpages ↗
    • Docssudo and sudoedit manualUbuntu manpages ↗
    • GuideLPIC-1 exam 101, objectives 103.1 and 103.4Linux Professional Institute ↗
    • Toolexplainshell, paste a command and see every part explainedexplainshell ↗

    Or get every quiz answer right and it completes itself.

    ← PreviousEditing files: nano, vim, and changing a server safelyNext lesson →Working with files: permissions, archives and links