Using the command line - known on MacOS as terminal - can be daunting given its use of written commands instead of a graphic interface. However, it is a very powerful tool and can often be a more productive and effective way to execute programs.
For example, what if you have 20 compressed files and want to search for "error" in all of them, then save those instances into a file? Without the command line, you would have to
But with command line, you can do this in one line!
zgrep -i "error" *.gz > output.txt
And that's just the tip of the iceberg! Now that you see the power, let's get into some of the basics of the command line.
A program is the actual code that will run to execute the action you request. A program will have parameters, also known as arguments or flags (see the history and details about their naming here. For all practical purposes, they are used interchangably), that can be added so you can invoke more specific actions. Together, the program and its arguments make up a command that you can execute in the command line.
The interface you see, like the terminal, to execute commands to the command line is also called the Command Line Interface (CLI). You'll often see CLI tools mentioned, so that's what people are referring to.
Arguments often have a short form that can be invoked with -
. The long form name can be invoked with --
.
You can invoke the short form with [command] -[argument 1] -[argument 2]
and with [command] -[argument 1][argument 2]
such as ls -la
.
First, lets go through a few basic knowledge about your computer.
Your operating system has folders, also known as directories. On MacOS and Linux, the root directory is /
. Every other directory can be reached from /
. When you open a new window in your command line, you are plopped into the default directory. Think of the directory system as a tree (hence the term "root"). So you can have /Users/daniloradovic/Desktop
.
The home/default directory is denoted as ~
. Mine is at /Users/daniloradovic
.
You will often see $
or some other symbol to denote the input to the command line - I will use $
.
What is actually running the program that you execute in the command line? This is called the shell. You can think of it as a program that runs your programs. You are entering commands to the shell as input and it can output results to the standard output (stdout) which is what you see outputted to the screen.
Some commands you may use very often and its a pain to keep writing them out. For example if you use git version control for and use git pull origin mainline --rebase
. Well, to make life easier you can give the command an alias and invoke the alias instead of the full command - for the aforementioned command you could use an alias like grm
.
First, identify which shell your machine is using. Run echo $0
- it will likely return "bash" or "zsh". You can also run echo $SHELL
.
If bash, you add the aliases and exports to ~/.bashrc
. If zsh, you add them to ~/.zshrc
. See my personal ~/.zshrc
setup here!
Run source ~/.bashrc
or source ~/.zshrc
and your aliases should now be useable.
Okay - let's jump into the commands! I am listing out my most commonly used beginners commands and arguments, so for some commands I list out more information than others when required. I will have another article for with some more commands that I use less commonly or are more intermediate.
You can run man [name of program]
to see the manual for the program. This gives you the description and a list of all the arguments that can be used with the program. This is super useful if you are having trouble finding an explanation for what a command and its arguments do, or want to see a list of arguments you haven't encountered before!
echo
will print out to the standard output (stdout) the arguments you provided it.
$echo "hello"
hello
$echo blah.txt
blah.txt
Notice that even though blah.txt is a file with contents, the actual string "blah.txt" is returned.
pwd
shows you which directory you are in at any given time
$ pwd
/Users/daniloradovic/Documents
cat
outputs the contents of a file to the standard output.
$ cat foo.txt
Hello there
cd
allows you to change directories. There are a bunch of very useful arguments, all of which tell the program which directory you want to go to.
When using actual paths instead of shortcut arguments, you can either input a relative or absolute path. Relative means relative to the current directory you are in, absolute means relative to the root.
An example of moving to an absolute path is:
$ cd /Users/daniloradovic/Desktop
You can move down into a folder or move up into a higher level folder with relative paths Relatively moving into a folder within your current directory:
$ cd Desktop
Relatively moving into a nested folder within your current directory:
$ cd Desktop/testFolder1/testFolder2
Here are some common shortcuts for relative movement. Relatively moving out of a folder one level up the tree:
$ pwd
Desktop/testFolder1/testFolder2
$ cd ..
$ pwd
Desktop/testFolder1
Relatively moving to the same folder you're in:
$ cd .
Note that cd ./testFolder1/testFolder2
and cd testFolder1/testFolder2
do the same thing. Moving to the last directory you were at before the current one:
$ pwd
Desktop/testFolder1/testFolder2
$ cd ..
$ pwd
Desktop/testFolder1
$ cd -
$ pwd
Desktop/testFolder1/testFolder2
ls
will print out the files and folders in your current directory
$ ls
Desktop
Pictures
Documents
arguments:
ls -l
will print out the files/folders in the current directory in addition to a lot of other information such as permissions, user/owner, size in bytes, and date created/
ls -a
will print out all files - even hidden ones. Hidden files often have a .
in front of it such as .zshrc
When doing ls -l
you will see something like
-rw-r--r--@
. This denotes read, write, execute permissions for each user group. A dash represents a lack of that permission for the group. Here is what it will look like giving everyone permissions to do all 3 actions for a file:
-rwxrwxrwx@
And for a directory:
drwxrwxrwx@
You can see rwx
is repeated 3 times. The first group represents permissions for Users (the current user), the second for Groups, the third for Others.
The d means the object is a directory, a dash represents everything else, such as a file or program.
Now that you know about permissions, you can use chmod to change the permissions of a file or directory. The general structure is chmod [groups]+[permissions] "[file]" where groups can be any permutation of u, g, and o and permissions can be any permutation of r, w, and x. For example to allow users, groups, and others to have full read, write, and execute permissions of a file, you would run
$ chmod ugo+rwx "foo.txt"
As a shorthand, you can use chmod a+rwx
to represent changes across all the permission groups.
sudo
in front of the rest of a normal command in order to execute the command as the root super user of the machine. This can sometimes let you take actions you normally wouldn't do, such as changing core values of the machine itself. This can be dangerous and corrupt your entire machine! So, only use sudo when you know it is safe.
mkdir
makes a directory in the path that you specify
$ ls
foo.txt
$ mkdir ~/Documents/bar
$ ls
bar
foo.txt
grep
allows you to search for strings within uncompressed file (of type .txt, .log, etc).
zgrep
is the same as grep
but works for compressed files like the .gz file type
$grep "error" ~/Documents/*
some useful arguments are:
-i
makes the query case-insensitive-c
does a count of occurrences-C [number]
prints out [number] lines before and after each occurrence's line--color=always
colors the query so it is easier to spot it in the output-m [num]
will print out just the first num
lines containing the query-h
will not print the file names in the output when you grep on more than one file-r
will search all files under a directory - based on the pattern matching of the file names, so you can use *
to search all files - with no depth limit-o
will only return the matching portion of the line. So if you specify foo*bar
then everything between foo and bar on a line (and including food and bar) will be returned
Another useful argument is -E
(or you can simply use egrep
) which allows for extended regular expression matching. This allows for easy use of logical operators and pattern matching:
grep -E "foo|bar" test.txt
will return lines that have either "foo" or "bar" and grep -E "foo.*bar" test.txt
will return lines that have both "foo" and "bar".
Note that printing to the standard output can be very inefficient and clunky; it also prevents easy further searches into the output and other manipulations of the data. So, often you can pair grep
/zgrep
with >
to print the output to a file as such
$ grep "error" ~/Documents/* > errors.txt
cp
stands for copy; you can provide it a source file or directory and destination file name or directory name
$ cp foo.txt bar.txt
Use -r
when copying a folder. Use -i
to display a confirmation message before doing the copying
rm
removes a file. To remove a directory, use rm -r
.
$ ls
$ rm bar.txt
$ ls
$ rm -r bar
Note: you can use -f
to force a deletion, but be very careful with this because it is irreversible!
find
will find specified files or directories in the path you specify. The basic usage is
find [path to search in] -name "[query string]"
searching for file names
$ find ~/Documents -iname "blah*"
./blah.txt
searching for file types
$ find ~/Documents -iname "*.txt"
./blah.txt
./foo.txt
./bar/test.txt
Note that it will return all results that match the criteria no matter how many levels deep in the directory from the starting path. To specify the depth, you can use -depth [number]
.
$ find ~/Documents -depth 4 -iname "*.txt"
This returns only the results at that depth, not everything from depth 1 to [number]. You can use -maxdepth [number]
to return everything from depth 1 to [number].
$ find ~/Documents -maxdepth 4 -iname "*.txt"
The above is just some simple examples; find
is extremely powerful and has a ton of arguments that can make your searches very advanced. Go check them out with man find
.
wc
stands for wordcount. Running the program with just the file as input yields
$ wc foo.txt
1 2 12 foo.txt
representing lines, words, and characters in the file. The below are my most commonly used arguments:
-c
returns the number of bytes in the file-l
returns the length of the file-m
returns the number of characters-w
returns the number of wordsThis allows you to run a command in the background so you can continue using your shell to run other commands. You append it to the end of the command you want to run in the background, such as grepping a folder containing hundreds of files
$ grep -i "error" * &
This is called a redirect. This allows the output of command 1 to be the output for a file. If the file already exists, its contents are overwritten.
$ echo "Hello there" > foo.txt
$ cat foo.txt
hello there
Is the same as > except it appends to the existing content of the file provided - if any. It does not overwrite the content
$ echo "Hello there" > foo.txt
$ echo "General Kenobi" >> foo.txt
$ cat foo.txt
Hello there
General Kenobi
[command 1 ]; [command 2]
allows you to execute command 1 first then execute command 2 after command 1 finishes. There is no interaction or information passed between the two.
$ cd ~/Documents; ls
foo.txt
bar
[command 1 ] && [command 2]
will execute command 1 and then only run command 2 if the first did not fail. A basic example is making an empty file and filling it with contents only if the file creation succeeded.
$ touch test.txt && echo "Hello there" > test.txt
$ cat test.txt
Hello there
Displays the free disk space. I use df -h for "human-readable" output.
$ df -h
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
/dev/disk1s5s1 234Gi 23Gi 57Gi 29% 563932 2448561428 0% /
devfs 191Ki 191Ki 0Bi 100% 662 0 100% /dev
$ fuser -n tcp 8000
$ lsof -i :8000
$ curl http://localhost:8000 -v
This will kill the process running on a port. Use the -9
argument to do a "non-catchable, non-ignorable kill"
$ kill -9 [port]
ssh
lets you access the command line of a destination host directly from a source machine, like your laptop! To connect, you may simply run
$ ssh [host@host.com]
For more advanced usage of ssh, take a look at this as a great resource for port forwarding/tunneling.
opens the file or directory in the default program. For me, directories open in Finder and files open in TextEdit
$ open blah.txt
$ open . // remember, . means the current directory
Vim is a program that allows you to view contents of a file and edit them; it is very powerful and has its own commands within the program itself. My preference is to use vim through ther command line when I need to quickly view, search, or edit files within minutes or needing to navigate across the file. In other cases like needing to work on Java code in long files or codebases where there are methods sprawled across many files, I'll use a code editor like VSCode.
Vim is still very important for software developers to be familiar with because a lot of times machines that you need to ssh into that hold your logs will have no option for easily viewing files besides Vim or Emacs (a similar program). It may also be difficult or a hassle to always open them in a code editor given they live on a machine other than your development machine. Below are just the bare basics for Vim that allow you to enter/exit the program. See my Command Line Usage Part Two article for a more practical guide on Vim.
To enter vim, just type vim [file name]
. To exit vim, type :q
or if that doesn't work try :q!
which will force quit.
vim works in modes, such as a write mode. To exit any mode, simply press the escape button.
There's a ton of other useful commands, which I will detail in another article. However, to just get you started with vim, enter into a file and press i
. Now you are in write mode and can edit the file.
Note: some machines don't have vim pre-installed. Most of those should have vi
installed, which is an earlier version of vim
(vim actually stands for Vi IMproved).