Introduction to Shell

Published

January 19, 2024

Manipulating files and directories

How does the shell compare to a desktop interface?

An operating system like Windows, Linux, or Mac OS is a special kind of program. It controls the computer’s processor, hard drive, and network connection, but its most important job is to run other programs.

Since human beings aren’t digital, they need an interface to interact with the operating system. The most common one these days is a graphical file explorer, which translates clicks and double-clicks into commands to open files and run programs. Before computers had graphical displays, though, people typed instructions into a program called a command-line shell. Each time a command is entered, the shell runs some other programs, prints their output in human-readable form, and then displays a prompt to signal that it’s ready to accept the next command. (Its name comes from the notion that it’s the “outer shell” of the computer.)

Typing commands instead of clicking and dragging may seem clumsy at first, but as you will see, once you start spelling out what you want the computer to do, you can combine old commands to create new ones and automate repetitive operations with just a few keystrokes.

  • They are both interfaces for issuing commands to the operating system.

Where am I?

The filesystem manages files and directories (or folders). Each is identified by an absolute path that shows how to reach it from the filesystem’s root directory: /home/repl is the directory repl in the directory home, while /home/repl/course.txt is a file course.txt in that directory, and / on its own is the root directory.

To find out where you are in the filesystem, run the command pwd (short for “print working directory”). This prints the absolute path of your current working directory, which is where the shell runs commands and looks for files by default.

Run pwd. Where are you right now?


pwd
/home/mburu/r_projects/m-mburu.github.io/datacamp/introduction_to_shell

How can I identify files and directories?

pwd tells you where you are. To find out what’s there, type ls (which is short for “listing”) and press the enter key. On its own, ls lists the contents of your current directory (the one displayed by pwd). If you add the names of some files, ls will list them, and if you add the names of directories, it will list their contents. For example, ls /home/repl shows you what’s in your starting directory (usually called your home directory).

Use ls with an appropriate argument to list the files in the directory /home/repl/seasonal (which holds information on dental surgeries by date, broken down by season). Which of these files is not in that directory?

ls seasonal
autumn.csv
spring.csv
summer.csv
winter.csv
winter.csv.bck

How else can I identify files and directories?

An absolute path is like a latitude and longitude: it has the same value no matter where you are. A relative path, on the other hand, specifies a location starting from where you are: it’s like saying “20 kilometers north”.

As examples:

If you are in the directory /home/repl, the relative path seasonal specifies the same directory as the absolute path /home/repl/seasonal. If you are in the directory /home/repl/seasonal, the relative path winter.csv specifies the same file as the absolute path /home/repl/seasonal/winter.csv. The shell decides if a path is absolute or relative by looking at its first character: If it begins with /, it is absolute. If it does not begin with /, it is relative

ls course.txt
ls seasonal/summer.csv
ls people
course.txt
seasonal/summer.csv
agarwal.txt

How can I move to another directory?

Just as you can move around in a file browser by double-clicking on folders, you can move around in the filesystem using the command cd (which stands for “change directory”).

If you type cd seasonal and then type pwd, the shell will tell you that you are now in /home/repl/seasonal. If you then run ls on its own, it shows you the contents of /home/repl/seasonal, because that’s where you are. If you want to get back to your home directory /home/repl, you can use the command cd /home/repl.

cd seasonal
pwd
ls
/home/mburu/r_projects/m-mburu.github.io/datacamp/introduction_to_shell/seasonal
autumn.csv
spring.csv
summer.csv
winter.csv
winter.csv.bck

How can I move up a directory?

The parent of a directory is the directory above it. For example, /home is the parent of /home/repl, and /home/repl is the parent of /home/repl/seasonal. You can always give the absolute path of your parent directory to commands like cd and ls. More often, though, you will take advantage of the fact that the special path .. (two dots with no spaces) means “the directory above the one I’m currently in”. If you are in /home/repl/seasonal, then cd .. moves you up to /home/repl. If you use cd .. once again, it puts you in /home. One more cd .. puts you in the root directory /, which is the very top of the filesystem. (Remember to put a space between cd and .. - it is a command and a path, not a single four-letter command.)

A single dot on its own, ., always means “the current directory”, so ls on its own and ls . do the same thing, while cd . has no effect (because it moves you into the directory you’re currently in).

One final special path is ~ (the tilde character), which means “your home directory”, such as /home/repl. No matter where you are, ls ~ will always list the contents of your home directory, and cd ~ will always take you home.

pwd ~/../.
/home/mburu/r_projects/m-mburu.github.io/datacamp/introduction_to_shell

How can I copy files?

You will often want to copy files, move them into other directories to organize them, or rename them. One command to do this is cp, which is short for “copy”. If original.txt is an existing file, then:

cp original.txt duplicate.txt creates a copy of original.txt called duplicate.txt. If there already was a file called duplicate.txt, it is overwritten. If the last parameter to cp is an existing directory, then a command like:

cp seasonal/autumn.csv seasonal/winter.csv backup copies all of the files into that directory.

cp seasonal/summer.csv  backup/summer.bck
cp seasonal/spring.csv seasonal/summer.csv backup
ls backup
agarwal.txt
autumn.csv
spring.csv
summer.bck
summer.csv

How can I move a file?

While cp copies a file, mv moves it from one directory to another, just as if you had dragged it in a graphical file browser. It handles its parameters the same way as cp, so the command:

mv autumn.csv winter.csv .. moves the files autumn.csv and winter.csv from the current working directory up one level to its parent directory (because .. always refers to the directory above your current location).

mv seasonal/spring.csv seasonal/summer.csv backup
ls backup
echo "spring and summer moved to backup"
ls seasonal
# move them back
mv backup/spring.csv backup/summer.csv seasonal
agarwal.txt
autumn.csv
spring.csv
summer.bck
summer.csv
spring and summer moved to backup
autumn.csv
winter.csv
winter.csv.bck

How can I rename files?

mv can also be used to rename files. If you run:

mv course.txt old-course.txt then the file course.txt in the current working directory is “moved” to the file old-course.txt. This is different from the way file browsers work, but is often handy.

One warning: just like cp, mv will overwrite existing files. If, for example, you already have a file called old-course.txt, then the command shown above will replace it with whatever is in course.txt.

cp seasonal/winter.csv backup/winter.csv 
mv seasonal/winter.csv seasonal/winter.csv.bck
ls seasonal
echo "winter.csv moved from backup for further examples"
mv backup/winter.csv seasonal/winter.csv
autumn.csv
spring.csv
summer.csv
winter.csv.bck
winter.csv moved from backup for further examples

How can I delete files?

We can copy files and move them around; to delete them, we use rm, which stands for “remove”. As with cp and mv, you can give rm the names of as many files as you’d like, so:

rm thesis.txt backup/thesis-2017-08.txt removes both thesis.txt and backup/thesis-2017-08.txt

rm does exactly what its name says, and it does it right away: unlike graphical file browsers, the shell doesn’t have a trash can, so when you type the command above, your thesis is gone for good.

cp seasonal/autumn.csv backup
rm seasonal/autumn.csv
ls seasonal
cp backup/autumn.csv seasonal
spring.csv
summer.csv
winter.csv
winter.csv.bck

How can I create and delete directories?

mv treats directories the same way it treats files: if you are in your home directory and run mv seasonal by-season, for example, mv changes the name of the seasonal directory to by-season. However, rm works differently.

If you try to rm a directory, the shell prints an error message telling you it can’t do that, primarily to stop you from accidentally deleting an entire directory full of work. Instead, you can use a separate command called rmdir. For added safety, it only works when the directory is empty, so you must delete the files in a directory before you delete the directory. (Experienced users can use the -r option to rm to get the same effect; we will discuss command options in the next chapter.)


#Without changing directories, delete the file agarwal.txt in the people directory.
cp people/agarwal.txt backup/agarwal.txt
rm people/agarwal.txt
ls people
rmdir people
mkdir people
cp backup/agarwal.txt people/agarwal.txt 
mkdir yearly
mkdir yearly/2017
ls
mkdir: cannot create directory ‘yearly’: File exists
mkdir: cannot create directory ‘yearly/2017’: File exists
backup
bottom.csv
count-records.sh
course.txt
date-range.sh
dates.sh
get-field.sh
intoroduction_shell.rmarkdown
intoroduction_shell.Rmd
introduction_to_shell.Rproj
last.csv
names.txt
num-records.out
people
range.out
range.sh
result.txt
seasonal
spring.csv
steps.txt
summer.csv
teeth.out
teeth.sh
temp.csv
yearly

Wrapping up

You will often create intermediate files when analyzing data. Rather than storing them in your home directory, you can put them in /tmp, which is where people and programs often keep files they only need briefly. (Note that /tmp is immediately below the root directory /, not below your home directory.) This wrap-up exercise will show you how to do that.

cd /tmp 
ls
mkdir /tmp/scratch
gdm3-config-err-b7pfou
hsperfdata_shinyproxy
quarto-session48e77b42
Rtmp0wKeLi
RtmpvP9jW5
scoped_dirsrgn3e
snap-private-tmp
systemd-private-c5ac1c15909343a4a214ea447616222f-bluetooth.service-JzWb6M
systemd-private-c5ac1c15909343a4a214ea447616222f-bolt.service-TlexIm
systemd-private-c5ac1c15909343a4a214ea447616222f-colord.service-qLFMQM
systemd-private-c5ac1c15909343a4a214ea447616222f-fwupd.service-AVogvg
systemd-private-c5ac1c15909343a4a214ea447616222f-ModemManager.service-aG2ZxZ
systemd-private-c5ac1c15909343a4a214ea447616222f-power-profiles-daemon.service-eH11QT
systemd-private-c5ac1c15909343a4a214ea447616222f-switcheroo-control.service-y36O1v
systemd-private-c5ac1c15909343a4a214ea447616222f-systemd-logind.service-ltwabB
systemd-private-c5ac1c15909343a4a214ea447616222f-systemd-oomd.service-wREtPS
systemd-private-c5ac1c15909343a4a214ea447616222f-systemd-resolved.service-z9o7s2
systemd-private-c5ac1c15909343a4a214ea447616222f-systemd-timesyncd.service-I1DToq
systemd-private-c5ac1c15909343a4a214ea447616222f-upower.service-ygra31
undertow.8080.3698114627944858908
undertow.9090.2177916117139773732
undertow-docbase.8080.13094858791798125321
undertow-docbase.9090.5730517480924681890

Manipulating data

How can I view a file’s contents?

Before you rename or delete files, you may want to have a look at their contents. The simplest way to do this is with cat, which just prints the contents of files onto the screen. (Its name is short for “concatenate”, meaning “to link things together”, since it will print all the files whose names you give it, one after the other.)

cat agarwal.txt
name: Agarwal, Jasmine
position: RCT2
start: 2017-04-01
benefits: full
cat course.txt
Introduction to the Unix Shell for Data Science

The Unix command line has survived and thrived for almost fifty years
because it lets people to do complex things with just a few
keystrokes. Sometimes called "the duct tape of programming", it helps
users combine existing programs in new ways, automate repetitive
tasks, and run programs on clusters and clouds that may be halfway
around the world. This lesson will introduce its key elements and show
you how to use them efficiently.

How can I view a file’s contents piece by piece?

You can use cat to print large files and then scroll through the output, but it is usually more convenient to page the output. The original command for doing this was called more, but it has been superseded by a more powerful command called less. (This kind of naming is what passes for humor in the Unix world.) When you less a file, one page is displayed at a time; you can press spacebar to page down or type q to quit.

If you give less the names of several files, you can type :n (colon and a lower-case ‘n’) to move to the next file, :p to go back to the previous one, or :q to quit.

Note: If you view solutions to exercises that use less, you will see an extra command at the end that turns paging off so that we can test your solutions efficiently.

less seasonal/spring.csv seasonal/summer.csv
Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom
2017-03-04,incisor
2017-03-12,wisdom
2017-03-14,incisor
2017-03-21,molar
2017-04-29,wisdom
2017-05-08,canine
2017-05-20,canine
2017-05-21,canine
2017-05-25,canine
2017-06-04,molar
2017-06-13,bicuspid
2017-06-14,canine
2017-07-10,incisor
2017-07-16,bicuspid
2017-07-23,bicuspid
2017-08-13,bicuspid
2017-08-13,incisor
2017-08-13,wisdom
2017-09-07,molarDate,Tooth
2017-01-11,canine
2017-01-18,wisdom
2017-01-21,bicuspid
2017-02-02,molar
2017-02-27,wisdom
2017-02-27,wisdom
2017-03-07,bicuspid
2017-03-15,wisdom
2017-03-20,canine
2017-03-23,molar
2017-04-02,bicuspid
2017-04-22,wisdom
2017-05-07,canine
2017-05-09,canine
2017-05-11,incisor
2017-05-14,incisor
2017-05-19,canine
2017-05-23,incisor
2017-05-24,incisor
2017-06-18,incisor
2017-07-25,canine
2017-08-02,canine
2017-08-03,bicuspid
2017-08-04,canine

How can I look at the start of a file?

The first thing most data scientists do when given a new dataset to analyze is figure out what fields it contains and what values those fields have. If the dataset has been exported from a database or spreadsheet, it will often be stored as comma-separated values (CSV). A quick way to figure out what it contains is to look at the first few rows.

We can do this in the shell using a command called head. As its name suggests, it prints the first few lines of a file (where “a few” means 10), so the command:

head seasonal/summer.csv

displays:

Date,Tooth
2017-01-11,canine
2017-01-18,wisdom
2017-01-21,bicuspid
2017-02-02,molar
2017-02-27,wisdom
2017-02-27,wisdom
2017-03-07,bicuspid
2017-03-15,wisdom
2017-03-20,canine

What does head do if there aren’t 10 lines in the file? (To find out, use it to look at the top of people/agarwal.txt.)


head -n 10 people/agarwal.txt
name: Agarwal, Jasmine
position: RCT2
start: 2017-04-01
benefits: full
  • displays as many lines as there are in the file

How can I type less?

One of the shell’s power tools is tab completion. If you start typing the name of a file and then press the tab key, the shell will do its best to auto-complete the path. For example, if you type sea and press tab, it will fill in the directory name seasonal/ (with a trailing slash). If you then type a and tab, it will complete the path as seasonal/autumn.csv.

If the path is ambiguous, such as seasonal/s, pressing tab a second time will display a list of possibilities. Typing another character or two to make your path more specific and then pressing tab will fill in the rest of the name.

echo "head seasonal/autumn.csv"
head seasonal/autumn.csv
echo "head seasonal/autumn.csv"
head seasonal/spring.csv
head seasonal/autumn.csv
Date,   Tooth
2017-01-05,canine
2017-01-17,wisdom
2017-01-18,canine
2017-02-01,molar
2017-02-22,bicuspid
2017-03-10,canine
2017-03-13,canine
2017-04-30,incisor
2017-05-02,canine
head seasonal/autumn.csv
Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom
2017-03-04,incisor
2017-03-12,wisdom
2017-03-14,incisor
2017-03-21,molar
2017-04-29,wisdom

How can I control what commands do?

You won’t always want to look at the first 10 lines of a file, so the shell lets you change head’s behavior by giving it a command-line flag (or just “flag” for short). If you run the command:

head -n 3 seasonal/summer.csv head will only display the first three lines of the file. If you run head -n 100, it will display the first 100 (assuming there are that many), and so on.

A flag’s name usually indicates its purpose (for example, -n is meant to signal “number of lines”). Command flags don’t have to be a - followed by a single letter, but it’s a widely-used convention.

Note: it’s considered good style to put all flags before any filenames, so in this course, we only accept answers that do that.


head -n 5 seasonal/winter.csv
Date,Tooth
2017-01-03,bicuspid
2017-01-05,incisor
2017-01-21,wisdom
2017-02-05,molar

How can I list everything below a directory?

In order to see everything underneath a directory, no matter how deeply nested it is, you can give ls the flag -R (which means “recursive”). If you use ls -R in your home directory, you will see something like this:


ls -R -F
.:
backup/
bottom.csv
count-records.sh
course.txt*
date-range.sh
dates.sh
get-field.sh
intoroduction_shell.rmarkdown
intoroduction_shell.Rmd*
introduction_to_shell.Rproj*
last.csv
names.txt
num-records.out
people/
range.out
range.sh
result.txt
seasonal/
spring.csv*
steps.txt
summer.csv*
teeth.out
teeth.sh
temp.csv
yearly/

./backup:
agarwal.txt*
autumn.csv*
summer.bck*

./people:
agarwal.txt*

./seasonal:
autumn.csv*
spring.csv*
summer.csv*
winter.csv*
winter.csv.bck*

./yearly:
2017/

./yearly/2017:

How can I get help for a command?

To find out what commands do, people used to use the man command (short for “manual”). For example, the command man head brings up this information:


HEAD(1)               BSD General Commands Manual              HEAD(1)

NAME
     head -- display first lines of a file

SYNOPSIS
     head [-n count | -c bytes] [file ...]

DESCRIPTION
     This filter displays the first count lines or bytes of each of
     the specified files, or of the standard input if no files are
     specified.  If count is omitted it defaults to 10.

     If more than a single file is specified, each file is preceded by
     a header consisting of the string ``==> XXX <=='' where ``XXX''
     is the name of the file.

SEE ALSO
     tail(1)

man automatically invokes less, so you may need to press spacebar to page through the information and :q to quit.

The one-line description under NAME tells you briefly what the command does, and the summary under SYNOPSIS lists all the flags it understands. Anything that is optional is shown in square brackets […], either/or alternatives are separated by |, and things that can be repeated are shown by …, so head’s manual page is telling you that you can either give a line count with -n or a byte count with -c, and that you can give it any number of filenames.

The problem with the Unix manual is that you have to know what you’re looking for. If you don’t, you can search Stack Overflow, ask a question on DataCamp’s Slack channels, or look at the SEE ALSO sections of the commands you already know.

man tail
tail -n +7 seasonal/spring.csv
TAIL(1)                                                                                                                User Commands                                                                                                                TAIL(1)

NAME
       tail - output the last part of files

SYNOPSIS
       tail [OPTION]... [FILE]...

DESCRIPTION
       Print the last 10 lines of each FILE to standard output.  With more than one FILE, precede each with a header giving the file name.

       With no FILE, or when FILE is -, read standard input.

       Mandatory arguments to long options are mandatory for short options too.

       -c, --bytes=[+]NUM
              output the last NUM bytes; or use -c +NUM to output starting with byte NUM of each file

       -f, --follow[={name|descriptor}]
              output appended data as the file grows;

              an absent option argument means 'descriptor'

       -F     same as --follow=name --retry

       -n, --lines=[+]NUM
              output the last NUM lines, instead of the last 10; or use -n +NUM to output starting with line NUM

       --max-unchanged-stats=N
              with --follow=name, reopen a FILE which has not

              changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files); with inotify, this option is rarely useful

       --pid=PID
              with -f, terminate after process ID, PID dies

       -q, --quiet, --silent
              never output headers giving file names

       --retry
              keep trying to open a file if it is inaccessible

       -s, --sleep-interval=N
              with -f, sleep for approximately N seconds (default 1.0) between iterations; with inotify and --pid=P, check process P at least once every N seconds

       -v, --verbose
              always output headers giving file names

       -z, --zero-terminated
              line delimiter is NUL, not newline

       --help display this help and exit

       --version
              output version information and exit

       NUM may have a multiplier suffix: b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024, GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.  Binary prefixes can be used, too: KiB=K, MiB=M, and so on.

       With  --follow  (-f), tail defaults to following the file descriptor, which means that even if a tail'ed file is renamed, tail will continue to track its end.  This default behavior is not desirable when you really want to track the actual name
       of the file, not the file descriptor (e.g., log rotation).  Use --follow=name in that case.  That causes tail to track the named file in a way that accommodates renaming, removal and creation.

AUTHOR
       Written by Paul Rubin, David MacKenzie, Ian Lance Taylor, and Jim Meyering.

REPORTING BUGS
       GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
       Report any translation bugs to <https://translationproject.org/team/>

COPYRIGHT
       Copyright © 2020 Free Software Foundation, Inc.  License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
       This is free software: you are free to change and redistribute it.  There is NO WARRANTY, to the extent permitted by law.

SEE ALSO
       head(1)

       Full documentation <https://www.gnu.org/software/coreutils/tail>
       or available locally via: info '(coreutils) tail invocation'

GNU coreutils 8.32                                                                                                     February 2022                                                                                                                TAIL(1)
2017-03-12,wisdom
2017-03-14,incisor
2017-03-21,molar
2017-04-29,wisdom
2017-05-08,canine
2017-05-20,canine
2017-05-21,canine
2017-05-25,canine
2017-06-04,molar
2017-06-13,bicuspid
2017-06-14,canine
2017-07-10,incisor
2017-07-16,bicuspid
2017-07-23,bicuspid
2017-08-13,bicuspid
2017-08-13,incisor
2017-08-13,wisdom
2017-09-07,molar

How can I select columns from a file?

head and tail let you select rows from a text file. If you want to select columns, you can use the command cut. It has several options (use man cut to explore them), but the most common is something like:

cut -f 2-5,8 -d , values.csv

which means “select columns 2 through 5 and columns 8, using comma as the separator”. cut uses -f (meaning “fields”) to specify columns and -d (meaning “delimiter”) to specify the separator. You need to specify the latter because some files may use spaces, tabs, or colons to separate columns.

What command will select the first column (containing dates) from the file spring.csv?

cut -f 1 -d , seasonal/spring.csv
Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28
2017-03-04
2017-03-12
2017-03-14
2017-03-21
2017-04-29
2017-05-08
2017-05-20
2017-05-21
2017-05-25
2017-06-04
2017-06-13
2017-06-14
2017-07-10
2017-07-16
2017-07-23
2017-08-13
2017-08-13
2017-08-13
2017-09-07

What can’t cut do?

cut is a simple-minded command. In particular, it doesn’t understand quoted strings. If, for example, your file is:

Name,Age
"Johel,Ranjit",28
"Sharma,Rupinder",26

then:

cut -f 2 -d , everyone.csv

will produce:

Age
Ranjit"
Rupinder"

rather than everyone’s age, because it will think the comma between last and first names is a column separator.

What is the output of cut -d : -f 2-4 on the line:

first:second:third: (Note the trailing colon.)

cut -d , -f 2-4 seasonal/summer.csv
Tooth
canine
wisdom
bicuspid
molar
wisdom
wisdom
bicuspid
wisdom
canine
molar
bicuspid
wisdom
canine
canine
incisor
incisor
canine
incisor
incisor
incisor
canine
canine
bicuspid
canine

How can I repeat commands?

One of the biggest advantages of using the shell is that it makes it easy for you to do things over again. If you run some commands, you can then press the up-arrow key to cycle back through them. You can also use the left and right arrow keys and the delete key to edit them. Pressing return will then run the modified command.

Even better, history will print a list of commands you have run recently. Each one is preceded by a serial number to make it easy to re-run particular commands: just type !55 to re-run the 55th command in your history (if you have that many). You can also re-run a command by typing an exclamation mark followed by the command’s name, such as !head or !cut, which will re-run the most recent use of that command.

cd seasonal
echo "head  winter.csv"
head  winter.csv
echo "latest head comman"
#!head
head  winter.csv
Date,Tooth
2017-01-03,bicuspid
2017-01-05,incisor
2017-01-21,wisdom
2017-02-05,molar
2017-02-17,incisor
2017-02-25,bicuspid
2017-03-12,incisor
2017-03-25,molar
2017-03-26,incisor
latest head comman

How can I select lines containing specific values?

head and tail select rows, cut selects columns, and grep selects lines according to what they contain. In its simplest form, grep takes a piece of text followed by one or more filenames and prints all of the lines in those files that contain that text. For example, grep bicuspid seasonal/winter.csv prints lines from winter.csv that contain “bicuspid”.

grep can search for patterns as well; we will explore those in the next course. What’s more important right now is some of grep’s more common flags:

  • -c: print a count of matching lines rather than the lines themselves
  • -h: do not print the names of files when searching multiple files
  • -i: ignore case (e.g., treat “Regression” and “regression” as matches)
  • -l: print the names of files that contain matches, not the matches
  • -n: print line numbers for matching lines
  • -v: invert the match, i.e., only show lines that don’t match
echo "grep molars seasonal/autumn.csv"

grep molar seasonal/autumn.csv

echo "grep -v -n molar seasonal/spring.csv"
grep -v -n molar seasonal/spring.csv

echo "grep -c incisor seasonal/autumn.csv  seasonal/winter.csv"
grep -c incisor seasonal/autumn.csv  seasonal/winter.csv
grep molars seasonal/autumn.csv
2017-02-01,molar
2017-05-25,molar
grep -v -n molar seasonal/spring.csv
1:Date,Tooth
2:2017-01-25,wisdom
3:2017-02-19,canine
4:2017-02-24,canine
5:2017-02-28,wisdom
6:2017-03-04,incisor
7:2017-03-12,wisdom
8:2017-03-14,incisor
10:2017-04-29,wisdom
11:2017-05-08,canine
12:2017-05-20,canine
13:2017-05-21,canine
14:2017-05-25,canine
16:2017-06-13,bicuspid
17:2017-06-14,canine
18:2017-07-10,incisor
19:2017-07-16,bicuspid
20:2017-07-23,bicuspid
21:2017-08-13,bicuspid
22:2017-08-13,incisor
23:2017-08-13,wisdom
grep -c incisor seasonal/autumn.csv  seasonal/winter.csv
seasonal/autumn.csv:3
seasonal/winter.csv:6

Why isn’t it always safe to treat data as text?

The SEE ALSO section of the manual page for cut refers to a command called paste that can be used to combine data files instead of cutting them up.

Read the manual page for paste, and then run paste to combine the autumn and winter data files in a single table using a comma as a separator. What’s wrong with the output from a data analysis point of view?

  • The last few rows have the wrong number of columns.
paste -d , seasonal/autumn.csv  seasonal/winter.csv
Date,   Tooth
,Date,Tooth
2017-01-05,canine
,2017-01-03,bicuspid
2017-01-17,wisdom
,2017-01-05,incisor
2017-01-18,canine
,2017-01-21,wisdom
2017-02-01,molar
,2017-02-05,molar
2017-02-22,bicuspid
,2017-02-17,incisor
2017-03-10,canine
,2017-02-25,bicuspid
2017-03-13,canine
,2017-03-12,incisor
2017-04-30,incisor
,2017-03-25,molar
2017-05-02,canine
,2017-03-26,incisor
2017-05-10,canine
,2017-04-04,canine
2017-05-19,bicuspid
,2017-04-18,canine
2017-05-25,molar
,2017-04-26,canine
2017-06-22,wisdom
,2017-04-26,molar
2017-06-25,canine
,2017-04-26,wisdom
2017-07-10,incisor
,2017-04-27,canine
2017-07-10,wisdom
,2017-05-08,molar
2017-07-20,incisor
,2017-05-13,bicuspid
2017-07-21,bicuspid
,2017-05-14,wisdom
2017-08-09,canine
,2017-06-17,canine
2017-08-16,canine
,2017-07-01,incisor
,2017-07-17,canine
,2017-08-10,incisor
,2017-08-11,bicuspid
,2017-08-11,wisdom
,2017-08-13,canine

Combining tools

How can I store a command’s output in a file?

All of the tools you have seen so far let you name input files. Most don’t have an option for naming an output file because they don’t need one. Instead, you can use redirection to save any command’s output anywhere you want. If you run this command:

head -n 5 seasonal/summer.csv

it prints the first 5 lines of the summer data on the screen. If you run this command instead:

head -n 5 seasonal/summer.csv > top.csv

nothing appears on the screen. Instead, head’s output is put in a new file called top.csv. You can take a look at that file’s contents using cat:

cat top.csv

The greater-than sign > tells the shell to redirect head’s output to a file. It isn’t part of the head command; instead, it works with every shell command that produces output.

#Combine tail with redirection to save the last 5 lines of seasonal/winter.csv in a file called last.csv
tail -n 5 seasonal/winter.csv > last.csv

How can I use a command’s output as an input?

Suppose you want to get lines from the middle of a file. More specifically, suppose you want to get lines 3-5 from one of our data files. You can start by using head to get the first 5 lines and redirect that to a file, and then use tail to select the last 3:

head -n 5 seasonal/winter.csv > top.csv
tail -n 3 top.csv

A quick check confirms that this is lines 3-5 of our original file, because it is the last 3 lines of the first 5.

tail -n 2 seasonal/winter.csv  > bottom.csv
head -n 1 bottom.csv
2017-08-11,wisdom

What’s a better way to combine commands?

Using redirection to combine commands has two drawbacks:

It leaves a lot of intermediate files lying around (like top.csv). The commands to produce your final result are scattered across several lines of history. The shell provides another tool that solves both of these problems at once called a pipe. Once again, start by running head:

head -n 5 seasonal/summer.csv

Instead of sending head’s output to a file, add a vertical bar and the tail command without a filename:

head -n 5 seasonal/summer.csv | tail -n 3

The pipe symbol tells the shell to use the output of the command on the left as the input to the command on the right. - Use cut to select all of the tooth names from column 2 of the comma delimited file seasonal/summer.csv, then pipe the result to grep, with an inverted match, to exclude the header line containing the word “Tooth”. cut and grep were covered in detail in Chapter 2, exercises 8 and 11 respectively.

# 
cut -f 2 -d , seasonal/summer.csv  | grep -v Tooth
canine
wisdom
bicuspid
molar
wisdom
wisdom
bicuspid
wisdom
canine
molar
bicuspid
wisdom
canine
canine
incisor
incisor
canine
incisor
incisor
incisor
canine
canine
bicuspid
canine

How can I combine many commands?

You can chain any number of commands together. For example, this command:

cut -d , -f 1 seasonal/spring.csv | grep -v Date | head -n 10

will:

  • select the first column from the spring data;
  • remove the header line containing the word “Date”; and
  • select the first 10 lines of actual data.

cut -d , -f 2 seasonal/summer.csv | grep -v Tooth | head -n 1
canine

How can I count the records in a file?

The command wc (short for “word count”) prints the number of characters, words, and lines in a file. You can make it print only one of these using -c, -w, or -l respectively.

grep 2017-07 seasonal/spring.csv | wc -l
3

How can I specify many files at once?

Most shell commands will work on multiple files if you give them multiple filenames. For example, you can get the first column from all of the seasonal data files at once like this:

cut -d , -f 1 seasonal/winter.csv seasonal/spring.csv seasonal/summer.csv seasonal/autumn.csv

But typing the names of many files over and over is a bad idea: it wastes time, and sooner or later you will either leave a file out or repeat a file’s name. To make your life better, the shell allows you to use wildcards to specify a list of files with a single expression. The most common wildcard is *, which means “match zero or more characters”. Using it, we can shorten the cut command above to this:

cut -d , -f 1 seasonal/*

or:

cut -d , -f 1 seasonal/*.csv
head -n 3 seasonal/s*
==> seasonal/spring.csv <==
Date,Tooth
2017-01-25,wisdom
2017-02-19,canine

==> seasonal/summer.csv <==
Date,Tooth
2017-01-11,canine
2017-01-18,wisdom

What other wildcards can I use?

The shell has other wildcards as well, though they are less commonly used:

  • ? matches a single character, so 201?.txt will match 2017.txt or 2018.txt, but not 2017-01.txt.
  • […] matches any one of the characters inside the square brackets, so 201[78].txt matches 2017.txt or 2018.txt, but not 2016.txt.
  • {…} matches any of the comma-separated patterns inside the curly brackets, so {.txt, .csv} matches any file whose name ends with .txt or .csv, but not files whose names end with .pdf.

Which expression would match singh.pdf and johel.txt but not sandhu.pdf or sandhu.txt?

  • {singh.pdf, j*.txt}

How can I sort lines of text?

As its name suggests, sort puts data in order. By default it does this in ascending alphabetical order, but the flags -n and -r can be used to sort numerically and reverse the order of its output, while -b tells it to ignore leading blanks and -f tells it to fold case (i.e., be case-insensitive). Pipelines often use grep to get rid of unwanted records and then sort to put the remaining records in order.

 cut -d , -f 2 seasonal/winter.csv | grep -v Tooth | sort -r
 
wisdom
wisdom
wisdom
wisdom
molar
molar
molar
molar
incisor
incisor
incisor
incisor
incisor
incisor
canine
canine
canine
canine
canine
canine
canine
bicuspid
bicuspid
bicuspid
bicuspid

How can I remove duplicate lines?

Another command that is often used with sort is uniq, whose job is to remove duplicated lines. More specifically, it removes adjacent duplicated lines. If a file contains:

2017-07-03
2017-07-03
2017-08-03
2017-08-03

then uniq will produce:

2017-07-03
2017-08-03

but if it contains:

2017-07-03
2017-08-03
2017-07-03
2017-08-03

then uniq will print all four lines. The reason is that uniq is built to work with very large files. In order to remove non-adjacent lines from a file, it would have to keep the whole file in memory (or at least, all the unique lines seen so far). By only removing adjacent duplicates, it only has to keep the most recent unique line in memory.


cut -d , -f 2 seasonal/winter.csv | grep -v Tooth | sort | uniq -c
      4 bicuspid
      1 canine
      6 canine
      6 incisor
      4 molar
      4 wisdom

How can I save the output of a pipe? The shell lets us redirect the output of a sequence of piped commands:

cut -d , -f 2 seasonal/*.csv | grep -v Tooth > teeth-only.txt

However, > must appear at the end of the pipeline: if we try to use it in the middle, like this:

cut -d , -f 2 seasonal/*.csv > teeth-only.txt | grep -v Tooth

then all of the output from cut is written to teeth-only.txt, so there is nothing left for grep and it waits forever for some input.

What happens if we put redirection at the front of a pipeline as in:

> result.txt head -n 3 seasonal/winter.csv
  • The command’s output is redirected to the file as usual.
> result.txt head -n 3 seasonal/winter.csv

How can I stop a running program?

The commands and scripts that you have run so far have all executed quickly, but some tasks will take minutes, hours, or even days to complete. You may also mistakenly put redirection in the middle of a pipeline, causing it to hang up. If you decide that you don’t want a program to keep running, you can type Ctrl + C to end it. This is often written ^C in Unix documentation; note that the ‘c’ can be lower-case.

  • use control + c

Wrapping up

To wrap up, you will build a pipeline to find out how many records are in the shortest of the seasonal data files.

wc -l seasonal/*.csv
wc -l seasonal/*.csv | grep -v total
wc -l seasonal/*.csv | grep -v total | sort -n | head -n 1
  21 seasonal/autumn.csv
  23 seasonal/spring.csv
  24 seasonal/summer.csv
  25 seasonal/winter.csv
  93 total
  21 seasonal/autumn.csv
  23 seasonal/spring.csv
  24 seasonal/summer.csv
  25 seasonal/winter.csv
  21 seasonal/autumn.csv

Batch processing

How does the shell store information?

Like other programs, the shell stores information in variables. Some of these, called environment variables, are available all the time. Environment variables’ names are conventionally written in upper case, and a few of the more commonly-used ones are shown below.

Variable    Purpose Value
HOME    User's home directory   /home/repl
PWD Present working directory   Same as pwd command
SHELL   Which shell program is being used   /bin/bash
USER    User's ID   repl

To get a complete list (which is quite long), you can type set in the shell.

Use set and grep with a pipe to display the value of HISTFILESIZE, which determines how many old commands are stored in your command history. What is its value?

set | grep HISTFILESIZE
BASH_EXECUTION_STRING='set | grep HISTFILESIZE'

How can I print a variable’s value?

A simpler way to find a variable’s value is to use a command called echo, which prints its arguments. Typing

echo hello DataCamp!

prints

hello DataCamp! If you try to use it to print a variable’s value like this:

echo USER

it will print the variable’s name, USER.

To get the variable’s value, you must put a dollar sign $ in front of it. Typing

echo $USER

prints

repl This is true everywhere: to get the value of a variable called X, you must write $X. (This is so that the shell can tell whether you mean “a file named X” or “the value of a variable named X”.)

  • The variable OSTYPE holds the name of the kind of operating system you are using. Display its value using echo.
echo $OSTYPE
linux-gnu

How else does the shell store information?

The other kind of variable is called a shell variable, which is like a local variable in a programming language.

To create a shell variable, you simply assign a value to a name:

training=seasonal/summer.csv

without any spaces before or after the = sign. Once you have done this, you can check the variable’s value with:

echo $training
seasonal/summer.csv
testing=seasonal/winter.csv
head -n 1 $testing
Date,Tooth

How can I repeat a command many times?

Shell variables are also used in loops, which repeat commands many times. If we run this command:

for filetype in gif jpg png; do echo $filetype; done it produces:

gif
jpg
png

Notice these things about the loop:

  • The structure is for …variable… in …list… ; do …body… ; done
  • The list of things the loop is to process (in our case, the words gif, jpg, and png).
  • The variable that keeps track of which thing the loop is currently processing (in our case, filetype).
  • The body of the loop that does the processing (in our case, echo $filetype).

Notice that the body uses $filetype to get the variable’s value instead of just filetype, just like it does with any other shell variable. Also notice where the semi-colons go: the first one comes between the list and the keyword do, and the second comes between the body and the keyword done.

Modify the loop so that it prints:

docx odt pdf

for filetype in docx odt pdf; do echo $filetype; done
docx
odt
pdf

How can I repeat a command once for each file?

You can always type in the names of the files you want to process when writing the loop, but it’s usually better to use wildcards. Try running this loop in the console:

for filename in seasonal/*.csv; do echo $filename; done It prints:

seasonal/autumn.csv
seasonal/spring.csv
seasonal/summer.csv
seasonal/winter.csv

because the shell expands seasonal/*.csv to be a list of four filenames before it runs the loop.

  • Modify the wildcard expression to people/* so that the loop prints the names of the files in the people directory regardless of what suffix they do or don’t have. Please use filename as the name of your loop variable.
for filename in people/*; do echo $filename; done
people/agarwal.txt

How can I record the names of a set of files?

People often set a variable using a wildcard expression to record a list of filenames. For example, if you define datasets like this:

datasets=seasonal/*.csv



#you can display the files' names later using:


for filename in $datasets; do echo $filename; done
seasonal/autumn.csv
seasonal/spring.csv
seasonal/summer.csv
seasonal/winter.csv

This saves typing and makes errors less likely.

If you run these two commands in your home directory, how many lines of output will they print?

files=seasonal/*.csv
for f in $files; do echo $f; done
seasonal/autumn.csv
seasonal/spring.csv
seasonal/summer.csv
seasonal/winter.csv
  • Four: the names of all four seasonal data files.

A variable’s name versus its value

A common mistake is to forget to use $ before the name of a variable. When you do this, the shell uses the name you have typed rather than the value of that variable.

A more common mistake for experienced users is to mis-type the variable’s name. For example, if you define datasets like this:

datasets=seasonal/*.csv and then type:

echo $datsets the shell doesn’t print anything, because datsets (without the second “a”) isn’t defined.

If you were to run these two commands in your home directory, what output would be printed?

files=seasonal/*.csv for f in files; do echo $f; done (Read the first part of the loop carefully before answering.)

  • One line: the word “files”. The variable f gets the value “files” for just one iteration of the loop.

How can I run many commands in a single loop?

Printing filenames is useful for debugging, but the real purpose of loops is to do things with multiple files. This loop prints the second line of each data file:

for file in seasonal/*.csv; do head -n 2 $file | tail -n 1; done It has the same structure as the other loops you have already seen: all that’s different is that its body is a pipeline of two commands instead of a single command.

Write a loop that prints the last entry from July 2017 (2017-07) in every seasonal file. It should produce a similar output to:

grep 2017-07 seasonal/winter.csv | tail -n 1 but for each seasonal file separately. Please use file as the name of the loop variable, and remember to loop through the list of files seasonal/*.csv (instead of ‘seasonal/winter.csv’ as in the example).

for file in seasonal/*.csv; do grep 2017-07 $file | tail -n 1; done
2017-07-21,bicuspid
2017-07-23,bicuspid
2017-07-25,canine
2017-07-17,canine

Why shouldn’t I use spaces in filenames?

It’s easy and sensible to give files multi-word names like July 2017.csv when you are using a graphical file explorer. However, this causes problems when you are working in the shell. For example, suppose you wanted to rename July 2017.csv to be 2017 July data.csv. You cannot type:

mv July 2017.csv 2017 July data.csv

because it looks to the shell as though you are trying to move four files called July, 2017.csv, 2017, and July (again) into a directory called data.csv. Instead, you have to quote the files’ names so that the shell treats each one as a single parameter:

mv 'July 2017.csv' '2017 July data.csv'

If you have two files called current.csv and last year.csv (with a space in its name) and you type:

rm current.csv last year.csv

what will happen:

  • will remove current.csv but not last year.csv throws an error
  • You can use single quotes, ’, or double quotes, “, around the file names.

How can I do many things in a single loop?

The loops you have seen so far all have a single command or pipeline in their body, but a loop can contain any number of commands. To tell the shell where one ends and the next begins, you must separate them with semi-colons:

for f in seasonal/*.csv; do echo $f; head -n 2 $f | tail -n 1; done

seasonal/autumn.csv 2017-01-05,canine seasonal/spring.csv 2017-01-25,wisdom seasonal/summer.csv 2017-01-11,canine seasonal/winter.csv 2017-01-03,bicuspid

Suppose you forget the semi-colon between the echo and head commands in the previous loop, so that you ask the shell to run:

for f in seasonal/*.csv; do echo $f head -n 2 $f | tail -n 1; done
  • What will the shell do?

  • Print one line for each of the four files.

  • shoul be

for f in seasonal/*.csv; do echo $f head -n 2 $f | tail -n 1 $f; done
2017-08-16,canine
2017-09-07,molar2017-08-04,canine2017-08-13,canine

Creating new tools

How can I edit a file?

Unix has a bewildering variety of text editors. For this course, we will use a simple one called Nano. If you type nano filename, it will open filename for editing (or create it if it doesn’t already exist). You can move around with the arrow keys, delete characters using backspace, and do other operations with control-key combinations:

  • Ctrl + K: delete a line.
  • Ctrl + U: un-delete a line.
  • Ctrl + O: save the file (‘O’ stands for ‘output’). You will also need to press Enter to confirm the filename!
  • Ctrl + X: exit the editor.
#nano names.txt

How can I record what I just did?

When you are doing a complex analysis, you will often want to keep a record of the commands you used. You can do this with the tools you have already seen:

  • Run history.
  • Pipe its output to tail -n 10 (or however many recent steps you want to save).
  • Redirect that to a file called something like figure-5.history. This is better than writing things down in a lab notebook because it is guaranteed not to miss any steps. It also illustrates the central idea of the shell: simple tools that produce and consume lines of text can be combined in a wide variety of ways to solve a broad range of problems
#Copy the files seasonal/spring.csv and seasonal/summer.csv to your home directory.
cp seasonal/spring.csv seasonal/summer.csv .
# Use grep with the -h flag (to stop it from printing filenames) and -v Tooth (to select lines that don't match the header line) to select the data records from spring.csv and summer.csv in that order and redirect the output to temp.csv.
grep -h -v Tooth spring.csv summer.csv > temp.csv
# Pipe history into tail -n 3 and redirect the output to steps.txt to save the last three commands in a file. (You need to save three instead of just two because the history command itself will be in the list.)
history | tail -n 3 > steps.txt

How can I save commands to re-run later?

You have been using the shell interactively so far. But since the commands you type in are just text, you can store them in files for the shell to run over and over again. To start exploring this powerful capability, put the following command in a file called headers.sh:

head -n 1 seasonal/*.csv

This command selects the first row from each of the CSV files in the seasonal directory. Once you have created this file, you can run it by typing:

bash headers.sh

This tells the shell (which is just a program called bash) to run the commands contained in the file headers.sh, which produces the same output as running the commands directly.

#nano dates.sh
bash dates.sh
Date
2017-01-05
2017-01-17
2017-01-18
2017-02-01
2017-02-22
2017-03-10
2017-03-13
2017-04-30
2017-05-02
2017-05-10
2017-05-19
2017-05-25
2017-06-22
2017-06-25
2017-07-10
2017-07-10
2017-07-20
2017-07-21
2017-08-09
2017-08-16
Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28
2017-03-04
2017-03-12
2017-03-14
2017-03-21
2017-04-29
2017-05-08
2017-05-20
2017-05-21
2017-05-25
2017-06-04
2017-06-13
2017-06-14
2017-07-10
2017-07-16
2017-07-23
2017-08-13
2017-08-13
2017-08-13
2017-09-07
Date
2017-01-11
2017-01-18
2017-01-21
2017-02-02
2017-02-27
2017-02-27
2017-03-07
2017-03-15
2017-03-20
2017-03-23
2017-04-02
2017-04-22
2017-05-07
2017-05-09
2017-05-11
2017-05-14
2017-05-19
2017-05-23
2017-05-24
2017-06-18
2017-07-25
2017-08-02
2017-08-03
2017-08-04
Date
2017-01-03
2017-01-05
2017-01-21
2017-02-05
2017-02-17
2017-02-25
2017-03-12
2017-03-25
2017-03-26
2017-04-04
2017-04-18
2017-04-26
2017-04-26
2017-04-26
2017-04-27
2017-05-08
2017-05-13
2017-05-14
2017-06-17
2017-07-01
2017-07-17
2017-08-10
2017-08-11
2017-08-11
2017-08-13

How can I re-use pipes?

A file full of shell commands is called a *shell script, or sometimes just a “script” for short. Scripts don’t have to have names ending in .sh, but this lesson will use that convention to help you keep track of which files are scripts.

Scripts can also contain pipes. For example, if all-dates.sh contains this line:

cut -d , -f 1 seasonal/*.csv | grep -v Date | sort | uniq

then:

bash all-dates.sh > dates.out

will extract the unique dates from the seasonal data files and save them in dates.out.

#cut -d , -f 2 seasonal/*.csv | grep -v Tooth | sort | uniq -c
bash teeth.sh > teeth.out
cat teeth.out
     15 bicuspid
      2 canine
     29 canine
     18 incisor
      1 molar
     10 molar
     17 wisdom

How can I pass filenames to scripts?

A script that processes specific files is useful as a record of what you did, but one that allows you to process any files you want is more useful. To support this, you can use the special expression $@ (dollar sign immediately followed by at-sign) to mean “all of the command-line parameters given to the script”.

For example, if unique-lines.sh contains sort $@ | uniq, when you run:

bash unique-lines.sh seasonal/summer.csv

the shell replaces $@ with seasonal/summer.csv and processes one file. If you run this:

bash unique-lines.sh seasonal/summer.csv seasonal/autumn.csv

it processes two data files, and so on.

As a reminder, to save what you have written in Nano, type Ctrl + O to write the file out, then Enter to confirm the filename, then Ctrl + X to exit the editor.

#nano count-records.sh
#tail -q -n +2 $@ | wc -l
bash count-records.sh seasonal/*.csv > num-records.out
cat num-records.out
89

How can I process a single argument?

As well as $@, the shell lets you use $1, $2, and so on to refer to specific command-line parameters. You can use this to write commands that feel simpler or more natural than the shell’s. For example, you can create a script called column.sh that selects a single column from a CSV file when the user provides the filename as the first parameter and the column as the second:

cut -d , -f $2 $1

and then run it using:

bash column.sh seasonal/autumn.csv 1

Notice how the script uses the two parameters in reverse order.

The script get-field.sh is supposed to take a filename, the number of the row to select, the number of the column to select, and print just that field from a CSV file. For example:

bash get-field.sh seasonal/summer.csv 4 2

should select the second field from line 4 of seasonal/summer.csv. Which of the following commands should be put in get-field.sh to do that?

#head -n $2 $1 | tail -n 1 | cut -d , -f $3
bash get-field.sh seasonal/summer.csv 4 2
bicuspid

How can one shell script do many things?

Our shells scripts so far have had a single command or pipe, but a script can contain many lines of commands. For example, you can create one that tells you how many records are in the shortest and longest of your data files, i.e., the range of your datasets’ lengths.

Note that in Nano, “copy and paste” is achieved by navigating to the line you want to copy, pressing CTRL + K to cut the line, then CTRL + U twice to paste two copies of it.

As a reminder, to save what you have written in Nano, type Ctrl + O to write the file out, then Enter to confirm the filename, then Ctrl + X to exit the editor.

#nano range.sh
#wc -l $@ | grep -v total | sort -n | head -n 1
#sort -n -r
cat range.sh
#Run the script on the files in the seasonal directory using seasonal/*.csv to match all of the files and redirect the output using > to a file called range.out in your home directory.
bash range.sh seasonal/*.csv > range.out
wc -l $@ | grep -v total | sort -n | head -n 1
wc -l $@ | grep -v total | sort -n -r | head -n 1

How can I write loops in a shell script?

Shell scripts can also contain loops. You can write them using semi-colons, or split them across lines without semi-colons to make them more readable:

#Print the first and last data records of each file.


for filename in $@
do
    head -n 2 $filename | tail -n 1
    tail -n 1 $filename
done

(You don’t have to indent the commands inside the loop, but doing so makes things clearer.)

The first line of this script is a comment to tell readers what the script does. Comments start with the # character and run to the end of the line. Your future self will thank you for adding brief explanations like the one shown here to every script you write.

As a reminder, to save what you have written in Nano, type Ctrl + O to write the file out, then Enter to confirm the filename, then Ctrl + X to exit the editor.

# Fill in the placeholders in the script date-range.sh with $filename (twice), head, and tail so that it prints the first and last date from one or more files.
# for filename in $@
# do
#     cut -d , -f 1 $filename | grep -v Date | sort | head -n 1
#     cut -d , -f 1 $filename | grep -v Date | sort | tail -n 1
# done

bash date-range.sh  seasonal/*.csv
2017-01-05
2017-08-16
2017-01-25
2017-09-07
2017-01-11
2017-08-04
2017-01-03
2017-08-13

What happens when I don’t provide filenames?

A common mistake in shell scripts (and interactive commands) is to put filenames in the wrong place. If you type:

tail -n 3

then since tail hasn’t been given any filenames, it waits to read input from your keyboard. This means that if you type:

head -n 5 | tail -n 3 somefile.txt

then tail goes ahead and prints the last three lines of somefile.txt, but head waits forever for keyboard input, since it wasn’t given a filename and there isn’t anything ahead of it in the pipeline.

Suppose you do accidentally type:

head -n 5 | tail -n 3 somefile.txt

What should you do next?

  • Press Ctrl + C to stop the running command.