The CLI isn't as scary as you think
VanJS
April 16th, 2025
Gavin Mogan
Senior Software Engineer at Digital Ocean.
https://www.gavinmogan.com
Hey everyone,
I'm Gavin. I am a Senior Software Developer at Digital Ocean, currently on the billing team. I've been there just over 5 years now.
Today I'm planning on covering the basics of the command line and linux, so it becomes a lot less intimidating. Dont worry about about remembering everything, as this is more about exposing to ideas, and my slides are downloadable. Don't worry if you know everything about linux already, as I guarentee with no money back, that you will still learn one thing new. I know this because when I looked at my past presentations, I learned some things I forgot about.
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.
Okay, a few notes before we get started.
While I know most developers these days, at least front end ones, will end up using an apple laptop, I'm going to cover linux as I know it the best, and but the apple cli, and windows with wsl, and even to some degrees powershell all have overlaps so it shouldn't be hard to adapt things.
Firstly, whenever you see a dollar sign followed by a command, its usually to indicate a command prompt. I've stuck with that convention here.
Upfront linux usually doesn't care about order of arguments, though some older apple versions do. So if something I cover doesn't work, try switching the order of the arguments.
Classicly linux like node packages, like to try and do one thing well, so you can combine the best tools for the job.
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
First things first. Getting help. There are a bunch of built in solutions but don't underestimate talking to other people.
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
Next up. Some of my fav keyboard shortcuts. They should all work in the default terminal as bash and zsh is in emacs modes.
The biggest life changer for me was reverse search. Most people get started in the terminal by typing out everything every time. Then you quickly realize that if you hit up or down, you can go back through previously typed out commands.
But ctrl+r, thats where speed starts to kick in.
Keyboard Shortcuts
Ctrl +r - Reverse search through your bash history
So on my little contrived demo here, you can see some past commands. Then I hit ctrl+r, and type out npm, to search back through my history, and run the same command as before. In this case, since its the last command, its probably easier to just hit up, but for when you have a larger history, this is way easier than trying to think about how many commands ago you ran something.
There are tools out there to make this even better though.
Where am I?
$ pwd
/home/gavinm
$ echo $PWD
/home/gavinm
Okay next up.
You can't really do anything if you don't know where you are. Most terminals will start you off in your home directory. On linux that is slash home, on apple its slash users, but you can configure it differently. I have some friends that have different terminal profiles that start them in different directories to make working on different projects easier. Vscode would be another one that drops you somewhere else, so its worth knowing where you are.
There is the pwd command, and the PWD variable.
pwd stands for print working directory, so i'm not sure why there's a variable for it. Outside of the cli, it also might be called cwd, which is current working directory.
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"
Now that you know where you are, you need to get to where you want to go.
Just like with urls, there are relative and absolute paths.
If it starts with a slash, its an absolute path.
If it doesn't, then its probably a relative path.
And finally, if your path contains special characters like spaces, you'll want to add backslash, or wrap everything in quotes.
Get back there
$ pwd
/home/gavinm
$ cd /home/gavinm/Develop
$ pwd
/home/gavinm/Develop
$ cd -
$ pwd
/home/gavinm
Now that you got in the habbit of moving around, its time to go back.
cd minus will get you back to the last directory you are in.
I often used it when I'm working in a large mono repo. I want to switch to a directory, run a command, then go back to the root of the project.
What is in this directory?
Simple Directory Listing
$ ls
Long Listing
$ ls -l
Descending(r) by Time(t)
$ ls -ltr
Hidden files
$ ls -a
Note, I'm using some customizations to make this more colorful.
First off you got the basic ls command. It'll show the files in the directory. I've used a couple of the common flags I use most often. You can add dash l to get details about files. Dash r will reverse the order. Dash t will sort by time. And dash a will show all hidden files.
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
When am i? The date command is very useful. I've used it to print out the date before and after a command so i kno how long it takes. There's also the built in time command which can tell you how long a command takes on your computer.
Knowing how to set the timezone temporarily is very useful too. Lots of commands will accept the TZ setting, but I've used TZ a lot to switch the date command to UTC since all our servers are running UTC time.
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
Testing HTTP. Another random set of tools. Most systems will have wget or curl installed. Both will work, but generally I use wget for downloading files, and curl for hitting APIs.
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
Next up. Tail and Head. Tail will take a stream, commonly a file, and give you the last few lines. Head will do the opposite, the start of the steam.
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 is very useful start for less manual commands. A single arrow to the right will take the output of a command and write it to a file. By default this is stdout. You can also be specific, like stream number 2, which is errors. Lastly there's the tee command which lets you save the output and print the output. I will talk about pipes soon.
Redirection input
Input from file
$ mysql < import.sql
Next up is the redirect the other way. A single arrow to the left will let you take the contents of a file and use it as input for a command. Mysql for example, you can send the contents of a sql file and run them with the mysql command.
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
So in shell, there's actually a lot of different kinds of wildcards. At the most basic level, the star is easy to understand. A star will essentially just match things. So star dot log will match anything that ends with dot log. Star by itself will match anything. This only works in a single directory though. You also have the ability to use double stars. They will match any number of sub directories. This is all called glob patterns if you want to look them up more.
Chaining
true && echo true
false || echo false
false; echo always
So at a basic level, bash has boolean operators live javascript. So you can do two ampersands and only run the second command if the first one succeeds. Two ors if it fails, and a semicolon that just splits up two statements. And yes, before someone corrects me to say its not actually success based, I'm not going cover error codes today.
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
Bash does have a lot of control statements. The ones I use the most are really if, while and for. I'll be covering for in detail in later slides. And if shouldn't really be a surprise to people. While loops are actually really useful in bash. The one i've seen the most is wait for something to startup. So while sql server isn't running, sleep and check again.
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 \;
Find can do a crazy amount of Stuff. The most common usages for me are finding files or directories with specific names, and running commands on the files you found. While researching this, I have found there's a new execdir which lets you run a command in the directory containing the files.
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
Okay, just before I start to show you all how to combine things, I have the final list of things I couldn't figure out how to put elsewhere. Cat takes one or more files and outputs them. Great for looking at logs or source code. Tac does the same thing but in reverse order. I've not once ever found a use for this knowledge, but now you all have it to. Less ad More will output files, but let you control the output, usually one page at a time. rm and rmdir remove files and direcotries. mkdir lets you create a directory, with dash -p super useful to make sub directories too. And lastly touch. Touch will create empty files, as well as update the last updated time. I mention all these because its often useful for the more advanced pipes and loops.
Power of pipes
With a few combos you can do anything
Pipes is where you can start combing things you've learned so far. Essentially they work left to right. First command pipe second command will take the output of the first one, and input it to the next command.
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 example. Lets say you have a web server access log. It has a line every time someone hits your server. You don't want to look through every line yourself, thats crazy. But you can narrow it down and look for anyone grabbing the fav icon. Thats still a lot of output. but from there, we can just grab one field. By default awk splits on spaces, so you can just grab part of the user agent, but with just one flag, you can grab the entire user agent.
If you pipe that into sort and uniq, you can actually start doing a bit of analysis, like finding out how many unique browsers hit your server and grabbed the favicon.
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
Next up is for loops. For takes in a variable, and then a list of values. So for this example, I make 3 directories. Gavin. Likes. Pie....cause I do. I've used things like this when I want to run the same command on multiple servers. But for can take in data from other sources too
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
And one option for that is just straight up shell wildcards. So for example, I can say for each file in this directory, rename the file to file dot bak. Yes there are better ways to do it, but they don't show off the power of for loops.
For Loops - subshells
$ for i in $(seq 1 10); do echo $i; done
1
2
3
4
5
6
7
8
9
10
You can also use sub shells. Sub shells are wrapped in dollar sign and parentheses. Seq is a cool command that just outputs numbers from start to finish. There's actually a bunch of options like padding it with 0s, but not really the point here.
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 example time.
I was making changes to a lot of json files, and when i do that, i often will forget to add or remove a comma. Instead of commiting, creating a pr, having tests fail, and repeating, I decided to only stage the files in git if they were valid.
:click:
So I created a little for loop that looped through all git files that were modified. Run them through jsonlint. And if they are considered valid, git add them.
After that I could look at files that were left and go in and fix them.
Just shows that you can build on the basics real quick.
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'
for the last few weeks I've been trying to remember to write down various oneliners I end up using over and over again. Every time I do there's more and more things I felt I should have covered.
I included this one because xargs is so powerful.
So in this case. Search all files for the phrase onedrop.
split on the colon so I just get the filename.
then using xargs, pass each filename into perl pie.
Yes apparently I could have used grep dash l, which would have just given me the filename, but for whatever reason I always remember this pattern, and I can apply it in all kinds of ways.
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
JQ is something I probably end up using every day in one way or another. I would say the most common is to pretty print json text. You can, as shown here, curl and pipe to jq.
Next most common for me has to be pulling out specific info. It can handle properties, as shown, but so much more, like arrays both numeric index, or filtering by other criteria.
Its also really great at producing content that can go into a for loop.
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;
Gron is a tool I really want to use more. It tries to make json more grepable by flattening everything out.
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;
so you can start combining them. Filter through jq, then flatten with gron.
The End
https://m.do.co/c/7d6859326b6a
As promised. If you want to try things out in a safe environment, feel free to use my referral code and create a droplet. If you already have an account, come talk to me or rodrigo and we can help you out.
I also had to cut so many things to make room for other things I really wanted to bring up such as git techniques, or fzf, or even the jump command. Some of which are fully fleshed out slides, some are just point form notes. Feel free to poke around the deck or ask me more.
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
I'm one of those weird backend developers that uses windows full time, and its been years since I used a apple, but from what I read online, most people are pretty comfortable with the built in Terminal app. When I was a apple user, iterm2 was the goto app.
--
I have a slide in a bit that covers ones that work on all oses.