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.
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.
- 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
- 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.
- 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
- The command itself (e.g.
git
,docker
,ls
,mv
). This says what program or utility you want to run. - Arguments (
file1.txt1
anddocs/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. - 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 tellsmv
to prompt the user before overwriting a file.
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.
- Windows/bash
- Mac/zsh
cd ~ # Go to home directory
touch .bashrc # Create .bashrc file if it doesn't exist
code .bashrc # Open .bashrc in vscode
cd ~ # Go to home directory
touch .zshrc # Create .zshrc file if it doesn't exist
code .zshrc # Open .zshrc 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.
- Windows/bash
- Mac/zsh
choco install jq
brew 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