The CLI isn't as scary as you think

easter egg a title screen of someone learning the cli in a classic 90s hacking style

VanJS

April 16th, 2025

Gavin Mogan

Senior Software Engineer at Digital Ocean. Digital Ocean Mascot, Sammy the Shark, holding a heart ballon

https://www.gavinmogan.com

Avatar Easter Egg

Upfront notes

  • Talking about linux. Rest are similar
  • $ is usually a shell prompt
  • Generally, Linux doesn't care what order CLI parameters are in.
  • Linux tools, like npm packages, like to do one thing and do them well.

Getting Help

Offline version

  • Most commands have a man(ual) pages
    $ man find
  • Shell functions only have help
    $ help for
  • Sometimes --help will return things
    $ ls --help

Keyboard Shortcuts

  • Ctrl+r - Reverse search through your bash history (My fav)
  • Up / Down - Prev/next in history
  • Ctrl+w - Delete previous word
  • Ctrl+k - Delete rest of line
  • Ctrl+u - Delete entire line
  • Ctrl+a - Move to start of line
  • Ctrl+e - Move to end of line

Keyboard Shortcuts

Ctrl+r - Reverse search through your bash history

Where am I?


              $ pwd
              /home/gavinm

              $ echo $PWD
              /home/gavinm
            

How to Change Directory

Most OSes uses forward slash - / as folder separator


            $ cd git
            $ pwd
            /home/gavinm/git
            $ cd /home/gavinm/git2
            $ pwd
            /home/gavinm/git2
          
Spaces and other characters should be escaped or quoted

              $ cd /home/gavinm/Project\ Dir
              $ cd "/home/gavinm/Project Dir"
            

Get back there


        $ pwd
        /home/gavinm

        $ cd /home/gavinm/Develop
        $ pwd
        /home/gavinm/Develop

        $ cd -
        $ pwd
        /home/gavinm
        

What is in this directory?

Simple Directory Listing

$ ls

Long Listing

$ ls -l

Descending(r) by Time(t)

$ ls -ltr

Hidden files

$ ls -a

When am I?


              $ TZ=America/Vancouver date
              Sun Apr 20 12:00:00 PDT 2025
            

Note: time command times a command, not time of day


              $ time sleep 30s
              sleep 30s  0.00s user 0.00s system 0% cpu 28.108 total
            

Testing HTTP

Curl and wget work kinda the same, but under different designs.

Wget is great at downloading files.

$ wget http://i.imgur.com/Ia48QDR.jpg

Curl is better at retrieving content.

$ curl https://httpcodes-8pkfp.ondigitalocean.app/json/404

Note: probably less true these days

What's happened recently?

Head will give you the first n lines

$ head -n 2 /var/log/nginx/presentations.gavinmogan.com.access.log
  presentations.gavinmogan.com:80 108.172.217.87 - - [24/Jun/2015:07:02:03 +0000] "GET /stats/ HTTP/1.1" 200 5566 "http://odin.kodekoan.com:4080/halkeye/gavinmogan.com/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36" "108.172.217.87"
  presentations.gavinmogan.com:80 108.172.217.87 - - [24/Jun/2015:07:02:03 +0000] "GET /stats/css/reveal.css HTTP/1.1" 200 48591 "http://presentations.gavinmogan.com/stats/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36" "108.172.217.87"
  

Tail will give you the last n lines

$ tail -n 2 /var/log/nginx/presentations.gavinmogan.com.access.log
  presentations.gavinmogan.com:80 162.158.64.218 - - [27/Jun/2015:03:35:46 +0000] "HEAD / HTTP/1.1" 403 166 "-" "Mozilla/5.0 (compatible; CloudFlare-AlwaysOnline/1.0; +http://www.cloudflare.com/always-online)" "162.158.64.218"
  presentations.gavinmogan.com:80 162.158.64.218 - - [27/Jun/2015:03:35:46 +0000] "GET / HTTP/1.1" 403 345 "-" "Mozilla/5.0 (compatible; CloudFlare-AlwaysOnline/1.0; +http://www.cloudflare.com/always-online) AppleWebKit/534.34" "162.158.64.218"
  

Common Options

  • -f will keep tailing
  • -F will restart tailing if file is truncated
  • -n #num# will only print out #num# number of lines

Redirection output

Output to file

$ echo "hi" > file.txt

Errors to file

$ curl http://fake.server 2> errors.txt

Output to file and console

$ echo "hi" | tee file.txt

Redirection input

Input from file

$ mysql < import.sql 

Wildcards (glob)

Will look find all directories that have a logs directory underneath it.

$ tail -F ~/Develop/*/logs/development.log

Will find all log files under all directories that have a log directory (one level deep)

$ tail -F ~/Develop/*/logs/*.log

Will find all log files under all directories that have a log directory (infinite levels deep)

$ tail -F ~/Develop/**/logs/*.log

Chaining

true && echo true
false || echo false
false; echo always

if and while statements

if true; then echo "its-a-me-truethy"; fi
while true; do echo "its-a-me-truethy"; done
for i in a b c; do echo $i; done

Serious wildcards

Find all directories

$ find -type d

Find all files

$ find -type f

Find all files ending in log


          $ find -name '*.log' # find all files ending in .log
          $ find -name '*.log' -exec ls {} \; # executes a command for each file
          $ find -name '*.log' -exec ls {} + # appends all files to one command
        

Run something in a directory with logs

$ find /var/log -name '*.log' -execdir pwd \;

Create Edit Update Destroy


          cat <file> # outputs contents
          tac <file> # outputs contents in reverse order
          less <file> # outputs content (controlled)
        

          rm <file> # Remove a file
          rmdir <dir> # Remove an empty directory
          mkdir <dir> # Create a directory (mkdir -p as bonus)
          # Create all the directories required to make the full path
          # (and doesn't error if already exists)
          mkdir -p <dir/subdir/subdir2>
        

          touch <file> # update timestamp/create empty file
        

Power of pipes

Mario going down a pipe and waving

With a few combos you can do anything

Grep and Awk Can Do anything


          $ grep /favicon.ico access.log
          # long output
        

          $ grep /favicon.ico access.log | awk '{print $17}'
          AppleWebKit/537.36
        

          $ grep /favicon.ico access.log | awk -F'"' '{print $6}'
          Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36
        

For Loops


          $ for i in gavin likes pie; do mkdir $i; done
          $ ls -l
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 gavin
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 likes
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 pie
        

For Loops - wildcards


          $ for i in *; do mv $i $i.bak; done
          $ ls -l
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 gavin.bak
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 likes.bak
          drwxr-xr-x 2 1000 1000 4096 Apr 15 04:23 pie.bak
        

For Loops - subshells


          $ for i in $(seq 1 10); do echo $i; done
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
        

Search and Replace

Sed, perl, python, etc

I prefer perl pie


        $ echo "Gavin likes pie" > file.txt
        $ cat file.txt
        Gavin likes pie
        $ perl -pi -e 's/Gavin/Bibi/' file.txt
        $ cat file.txt
        Bibi likes pie
        

Real world example


          for i in $(git ls-files -m); do
            jsonlint $i && git add $i;
          done
        

Real World Example - Find and Replace multiple files


          grep -r onedr0p . | \
            awk -F: '{print $1}' | \
            xargs perl -pi -e 's#ghcr.io/onedr0p#ghcr.io/home-operations#g'
        

Non standard tools - JQ

https://jqlang.org/


        $ curl -qs 'https://jsonplaceholder.typicode.com/todos/1' | jq '.'
        {
          "userId": 1,
          "id": 1,
          "title": "delectus aut autem",
          "completed": false
        }
        $ curl -qs 'https://jsonplaceholder.typicode.com/todos/1' | jq '.id'
        1
        $ curl -qs 'https://jsonplaceholder.typicode.com/todos/1' | jq '.completed'
        false
        

Non standard tools - gron

https://github.com/tomnomnom/gron


        $ curl -qs 'https://jsonplaceholder.typicode.com/todos/1' | gron
        json = {};
        json.completed = false;
        json.id = 1;
        json.title = "delectus aut autem";
        json.userId = 1;
        

Eg curl + jq + gron


        $ curl -qs 'https://jsonplaceholder.typicode.com/todos' | jq '[.[] | select(.completed == true)][0]' | gron
        json = {};
        json.completed = true;
        json.id = 4;
        json.title = "et porro tempora";
        json.userId = 1;
        

The End

https://m.do.co/c/7d6859326b6a DigitalOcean Referral Badge

Extra Stuffsssss

Slides that were already done but decided against bringing them up

Debugging

Run a script in debug mode

$ bash -x script.sh

Enable debugging right now

$ set -x

SSH

What are ssh keys?

Why would you want them?

How do you use them?

man ssh_config

Whats going on?

  • ps xf -A (My Favourite)
  • ps aux (Very portable)
    • More w's with ps = more wide
    • ps auxwww
  • pstree
  • top

Home Directories


          $ cd
          /home/gavinm
        

          $ cd ~
          /home/gavinm
        

          $ cd $HOME
          /home/gavinm
        
Another users home dir

          $ cd ~halkeye
          /home/halkeye
        

How to get there? - Apple

  • Terminal.app
  • Iterm2