Skip to main content

Bash

A Shell, such as Bash (Bourne-Again SHell), or zsh (Z-shell) allows you to interact with your operating system through a command line. Command line interfaces (CLIs) often allow for enhanced productivity compared to graphical user interfaces (GUIs) for many tasks, as interacting via a keyboard is quicker than with a mouse. Shell commands are also scriptable, meaning you can more easily automate tasks and encode repeatable operation.

Shell commands are inputted via a terminal application. In the same way there are many different programming languages which can be mixed and matched with many different text editors, there are many different shells and terminal applications. We teach the very basics of bash in 61d because it is the most widely used shell and is the default shell on most linux distributions.

note

Bash and zsh are different, but their difference largely lie in how they are configured, not their actual command syntax. For clarity, we will just use the work bash, but know that everything we cover and discuss about bash applies to zsh as well.

Why use a command line interface? (CLI)

Most people have been using GUIs (websites, mobile apps, desktop apps) for their entire life and are generally familiar with how to navigate them. The initial learning curve of getting familiar with CLIs often discourages junior developers from relying on them, but even if you don't become a CLI power user, basic familiarity and competency with CLI tools is a necessity for the following core reasons.

  1. Efficiency: A website like Amazon Web Services has thousands if not tens of thousands of menus and pages to display all of the possible actions you can take. It could take 5-7 clicks and page reloads to get where you need to go (assuming you get there first try), or you could just run a command through your terminal using the AWS CLI.

As an example, this one command uses the GitHub CLI to create a private github repository titled "my-repo" with a .gitignore file and a README.md file, and clones it locally. Doing the equivalent from the github.com home page would take a minimum of 5 click and two page reloads not including filling in the repository information.

gh repo create my-repo --add-readme --clone --description "test repo from command line" --gitignore Node --private
  1. Scripting and Repeatability: If you need to document how to perform some action on your AWS instance, it is much simpler to just write down a CLI command than write down which sequence of menus you need to navigate through (which may change over time of course). Further, if you need to run a series of commands to properly configure or set up something within AWS, writing them down in a script ensures that the exact same configuration is generated each time. Running a script is more deterministic and repeatable than a human manually clicking through menus.
  2. Remote Access: A web server running your code in a data center somewhere does not have a monitor attached to it. The only practical way to interact with remote servers is through command line tools.

All of the general reasons why one would want to use a CLI are the same reasons that it makes sense to learn bash, which is essentially just a CLI for you operating system. It is generally quicker to make, move, and manage files through a command line than a file explorer.

CLI Command structure

mv -i file1.txt docs/file1.txt

A typical command has three parts

  1. The command itself (e.g. git, docker, ls, mv). This says what program or utility you want to run.
  2. Arguments (file1.txt1 and docs/file1.txt in our example). These are the inputs to the command, and their order is important for the command execution. In this case, the first argument is the source file location, and the second argument is the destination file.
  3. Options/Flags (-i in our example). These modify the behavior of the command and typically do not have to be passed in any particular order. They are usually preceded by a - or --. In this case, the -i flag tells mv to prompt the user before overwriting a file.
important

Most of the time, CLI interfaces have tools for explaining what options are available, either by pressing tab ↹ to list autocomplete options, or through a flag like --help.

If you forget what git command options are available to you, run:

git --help

Setting Up Aliases

Long commands can be cumbersome to remember and type out every single time, especially if you run them dozens of times a day. Fortunately, you can alias a command to a shorter name. For example, you can alias git status to gs by updating either your .bashrc or .zshrc file on windows or MacOS respectively.

cd ~ # Go to home directory
touch .bashrc # Create .bashrc file if it doesn't exist
code .bashrc # Open .bashrc in vscode

Then add an alias like so:

alias gs='git status'

After adding an alias to the file, you will need to restart your terminal or run source ~/.bashrc or source ~/.zshrc to apply the changes. You should nwo be able to use your alias in the terminal.

Basic Scripting Example

In addition to being a CLI interface for your operating system, bash is a programming language that can be used to write scripts.

The simplest way to create a script is to add if to your .bashrc or .zshrc. Let's look at an example script which adds a line to a package.json file (the use of which we will cover once we talk about React) using the jq command line utility.

choco install jq

Verify your install

jq --help

Then add the following script to your .bashrc or .zshrc and run source ~/.bashrc or source ~/.zshrc.

# Add package json script
pjs() {
# Check that there are two arguments passed
if [ "$#" -ne 2 ]; then
echo "Usage: add_script_to_package_json <script_name> <script_body>"
return 1
fi

SCRIPT_NAME=$1
SCRIPT_BODY=$2

# Check that there is a package.json file
if [ ! -f package.json ]; then
echo "Error: package.json not found in the current directory."
return 1
fi

# Copy the contents of the existing package.json and the new script to a temp file
jq ".scripts[\"$SCRIPT_NAME\"] = \"$SCRIPT_BODY\"" package.json > temp.json

# Replace existing package.json with the temp file
mv temp.json package.json

# Log a that the operation was successful
echo "Script '$SCRIPT_NAME' added to package.json successfully."
}

Sometimes you may have a script which you will only use on a specific project. In this case, we can create a specific file just for our one script and make it executable, instead of storing it in a .bashrc or .zshrc file.

Bash Quick Reference

pwd - Print working directory

Shows you where you are in the file system.

pwd

ls - List current directory contents

ls
ls -a # List all files including hidden files and dot files like .gitignore

cd - Change directory

cd path/to/directory
cd .. # Move up one directory
cd ../../ # Move up two directories
cd - # Move to the previous directory
cd ~ # Move to the home directory

touch - Create an empty file

If the file already exists, touch will not modify the file's contents.

touch newfile.txt # Create a new file
touch file1.txt file2.txt # Create multiple files

mkdir - Create a new directory

mkdir newdir # Create a new directory
mkdir -p path/to/newdir # Create a new directory and any necessary parent directories

mv - Move or rename a file

mv oldname.txt newname.txt # Rename a file
mv myfile.txt newdir/mytextfile.txt # Move a file

cp - Copy a file

cp myfile.txt newdir/ # Copy a file to a new directory
cp -r mydir newdir/ # Copy a directory to a new directory

rm - Remove a file or directory

rm myfile.txt # Remove a file
rm -rf mydir # Remove a directory and all its contents

echo - Print text to console

echo "Hello, World!"

cat - Print files to standard output

cat myfile.txt # Print the contents

history - Show command history

Most of the time, you want to search through your command history to find a command you ran earlier. A more useful way to do this is often to use Ctrl + R to search through your history, rather than just printing the entire history.

history # print full history to standard output
history | tail -n 10 # Show the last 10 commands

tree - Show directory structure

tree
tree -L 2 # Show the directory structure to a depth of 2
tree -L 2 -I "node_modules" "build" # Show the directory structure to a depth of 2, ignoring node_modules and build directories
tree --gitignore # Automatically ignore all files ignored by git based on a .gitignore file

alias - Create a shortcut for a command

Running the alias command will only create the alias for the current terminal session. To make it accessible in every session, add the alias to your .bashrc or .zshrc file as explained in Setting up aliases.

alias gs='git status'

You can also use the which command to see what a command is aliased to or run alias without any arguments to print all of your aliases.

which gs # gs: aliased to git status
alias # print all aliases in current terminal session

clear - Clear the terminal screen

clear

curl - Make a network request from the command line

Curl supports all major options one would need to make network requests. The basic usage is to provide a URL to make a GET request.

# Gets the contents of the page you are viewing right now and prints it to the console
curl https://raw.githubusercontent.com/CS61D/website/main/docs/Readings/bash.mdx

Often times api documentation will show how their api can be called directly through a curl request. In this example, we can call the GitHub api to list the issues for a repository. In this example, we include request headers which specify the API version and the response type as json.

curl -L \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/CS61D/website/issues # 61d website