Little big man
Getting to know the small but popular ls command
Summary
The ls command is much more powerful than the day-to-day ls -l functionality we're all most familiar with. Mo demonstrates the listing capabilities of a number of useful ls switches.
(3,100 words)
My series on "small fry" Unix commands did, I admit, overlook perhaps the smallest and also the most used command of them all.
The humble ls command is known by most users as ls, or ls -l. Some even know
ls -F, but the ls command is much more powerful than
these few options. This simple command provides several versions of listing
functionality, at least a few of which you'll surely want to add to your arsenal after
you've read this month's column.
The basic ls command will list the contents of a directory in alphabetical
order and in four columns (usually), as in the following listing:
romany|mjb $ ls
PERSONALITY copyxp listen.tar smit.log
Personly.dat dwksave lamage.cpio.Z smit.script
Power.dt ezcomp log.txt src.224
STARTUP eztree mbox trash
acutime.cbl fax mftime.cbl wisperr.log
alpha_port holdit.c mjb.grodin xerox
amerc junk.txt necesito
bin justio.c open9ktrack.log
contest.txt listen setdwks
So far so good, but this command overlooks a few files that should be on the list.
By default, any file that starts with a period (.) is not displayed.
To get these files to display, use the -a option. A command-line option that is
preceded by a dash (-) is often called a switch. The following is the output of
ls -a:
romany|mjb $ ls -a
. acutime.cbl holdit.c necesito
.. alpha_port junk.txt open9ktrack.log
.exrc amerc justio.c setdwks
.profile bin listen smit.log
.profile.031996 contest.txt listen.tar smit.script
.sh_history copyxp lamage.cpio.Z src.224
PERSONALITY dwksave log.txt trash
Personly.dat ezcomp mbox wisperr.log
Power.dt eztree mftime.cbl xerox
STARTUP fax mjb.grodin
romany|mjb $
This listing includes the dot (.) and double-dot (..) entries, representative of
the current directory and the parent directory, which are part of any
directory, as well as four new files that begin with a period.
The dot (.) and double-dot (..) entries are rarely wanted in a directory
listing. They can be eliminated by using the ls -A option, which does the same
job as ls -a, but skips the dot and double-dot entries, as in the following
listing:
romany|mjb $ ls -A
.exrc amerc justio.c setdwks
.profile bin listen smit.log
.profile.031996 contest.txt listen.tar smit.script
.sh_history copyxp lamage.cpio.Z src.224
PERSONALITY dwksave log.txt trash
Personly.dat ezcomp mbox wisperr.log
Power.dt eztree mftime.cbl xerox
STARTUP fax mjb.grodin
acutime.cbl holdit.c necesito
alpha_port junk.txt open9ktrack.log
romany|mjb $
But what are these entries? Are these directories, files, or what? Command ls -l
will give you the answers you seek, but it will also fill the screen with information
you may not need.
Instead, use the -F option. This switch appends quick, identifying
abbreviations to the end of each entry name to help you identify entries.
An executable file or program has an asterisk (*) appended to the entry name; a
directory takes a slash (/), and a link to another file takes an at sign (@).
The output of ls -F follows:
romany|mjb $ ls -F
PERSONALITY copyxp/ listen.tar smit.log
Personly.dat dwksave/ lamage.cpio.Z smit.script
Power.dt ezcomp/ log.txt src.224/
STARTUP* eztree/ mbox trash/
acutime.cbl fax/ mftime.cbl wisperr.log
alpha_port@ holdit.c mjb.grodin/ xerox/
amerc/ junk.txt necesito/
bin/ justio.c open9ktrack.log
contest.txt* listen/ setdwks
romany|mjb $
Combining -A and -F
Things are looking better, but now we've lost the files preceded by a period. To get
them back again, we combine the -A and -F flags. In the listing below, the asterisk flag
(*) indicates that .profile, .profile.031996, STARTUP, and contest.txt are
executable files. The alpha_port entry is a link to some other entry. The
entries terminated with a slash (/) are directories.
romany|mjb $ ls -AF
.exrc amerc/ justio.c setdwks
.profile* bin/ listen/ smit.log
.profile.031996* contest.txt* listen.tar smit.script
.sh_history copyxp/ lamage.cpio.Z src.224/
PERSONALITY dwksave/ log.txt trash/
Personly.dat ezcomp/ mbox wisperr.log
Power.dt eztree/ mftime.cbl xerox/
STARTUP* fax/ mjb.grodin/
acutime.cbl holdit.c necesito/
alpha_port@ junk.txt open9ktrack.log
romany|mjb $
If the output is to a terminal, the ls command lists files in multiple columns.
If the output is piped to some other program, the multiple columns disappear
and the output is formatted into a single column. Try piping all the work
we've done so far through more and a different picture emerges, as you see in
the following two screens of output for ls -AF|more:
romany|mjb $ ls -AF|more
.exrc
.profile*
.profile.031996*
.sh_history
PERSONALITY
Personly.dat
Power.dt
STARTUP*
acutime.cbl
alpha_port@
amerc/
bin/
contest.txt*
copyxp/
dwksave/
ezcomp/
eztree/
fax/
holdit.c
junk.txt
justio.c
listen/
listen.tar
--More--
dwksave/
ezcomp/
eztree/
fax/
holdit.c
junk.txt
justio.c
listen/
listen.tar
lamage.cpio.Z
log.txt
mbox
mftime.cbl
mjb.grodin/
necesito/
open9ktrack.log
setdwks
smit.log
smit.script
src.224/
trash/
wisperr.log
xerox/
romany|mjb $
The -C flag
You can use the -C flag to force the output into multiple-column format, regardless of whether
output is to a terminal or not. In the following listing, the output lists in four columns even though it's piped through more.
romany|mjb $ ls -AFC|more
.exrc amerc/ justio.c setdwks
.profile* bin/ listen/ smit.log
.profile.031996* contest.txt* listen.tar smit.script
.sh_history copyxp/ lamage.cpio.Z src.224/
PERSONALITY dwksave/ log.txt trash/
Personly.dat ezcomp/ mbox wisperr.log
Power.dt eztree/ mftime.cbl xerox/
STARTUP* fax/ mjb.grodin/
acutime.cbl holdit.c necesito/
alpha_port@ junk.txt open9ktrack.log
romany|mjb $
One thing about this directory listing bothers me. I tend to read the listings
across from left to right, but you can see that this listing is actually a
snaking column. The first entries fill column 1, the next in order start at
the top of column 2, and so on. This gets annoying when the directory entry
requested is longer than a page, because the top portion of each of the snaking
columns appears on the first page.
In order to correct this, replace the -C option with the -x option, which will
print entries across rather than down. The following example is the result of
ls -AFx; the work we've done already is combined with a left-to-right listing.
romany|mjb $ ls -AFx
.exrc .profile* .profile.031996* .sh_history
PERSONALITY Personly.dat Power.dt STARTUP*
acutime.cbl alpha_port@ amerc/ bin/
contest.txt* copyxp/ dwksave/ ezcomp/
eztree/ fax/ holdit.c junk.txt
justio.c listen/ listen.tar lamage.cpio.Z
log.txt mbox mftime.cbl mjb.grodin/
necesito/ open9ktrack.log setdwks smit.log
smit.script src.224/ trash/ wisperr.log
xerox/
romany|mjb $
We're gradually refining our ls options, but there's still one big hole in
this latest version. If I want to see entries beginning with l, I would type
the command
ls -AFx l*
However, in the result shown below, the output is not at all what I was
expecting:
romany|mjb $ ls -AFx l*
listen.tar lamage.cpio.Z log.txt
listen:
Makefile atable cobstat.h crec.h crid.a
cstmtest.wcb ctrlio.c dio.h disam.h dtype.h
filetbl.c* filetbl.o gp.h iocode.h kcsio.h
kisam.h kplatfrm.h kwisp.h link.c link.h
ll.c ll.h lmxcap.c lmxcnvrt.wcb lmxcvt.c
lmxdoc.c lmxdsp.c lmxexec.c lmxfile.c lmxfld.c
lmxflded.c lmxflist.c lmxfrm.c lmxglb.c lmxglb.h
lmxlmx.c lmxload.c lmxlog.c lmxmain.c lmxmenu.c
lmxnaf.c lmxout.c lmxparse.c lmxprs.c lmxrec.c
lmxsel.c lmxsort.c lmxwsel.c lstr.a ntable
parminfo.h readme.txt rlmx.h rptglb.h rptprm.h
rptsrt.h runcbl.a* shrthand.h vscracu.c vscracu.o
wispscr.c wispscr.h
romany|mjb $
Instead of listing the four entries that begin with l, this command lists
three files, and then expands the contents of the fourth entry, which is a
directory named "listen." What I really wanted was something like this:
romany|mjb $ ls -AFx l*
listen/ listen.tar lamage.cpio.Z log.txt
The -d switch
In the normal course of processing, if the file specification (the l* in this
example) of an ls command matches a directory, the directory is not simply
listed, but is itself expanded. This behavior can be suppressed by using the
-d option. Finally, below we get the output that we wanted by using ls -AFxd
l*.
romany|mjb $ ls -AFxd l*
listen/ listen.tar lamage.cpio.Z log.txt
romany|mjb $
There is a catch to using the -d switch. You must provide something as a
filename argument. Try typing ls -AFxd with no argument or filenames and you get
back:
romany|mjb $ ls -AFxd
./
romany|mjb $
What happened to all the files? The ls command without any arguments uses the
period (.) as the default argument. Remember, the period is a stand-in for the current
directory. Using a simpler version of the command with just the -d switch, you can see
what's happening. The command
ls -d
has the period (.) added as the default argument and effectively becomes
ls -d .
In English this becomes: list the current directory, but don't expand or list
its contents. The output of this command is shown below -- the period (.) is not
expanded.
romany|mjb $ ls -d
.
romany|mjb $
The -F flag simply adds the slash after the period as in the earlier
example, letting us know that the current directory is a directory. You have to
remember this when using the -d switch. Actually, the rule on using -d is a bit
longer. If there are no arguments to a command containing the -d switch, or if all
the arguments to the command are directories, the directories will not be
expanded. For example, attempting to use a -d argument on your home directory will
cause this. In the example below, -d provides a directory listing of the
$HOME directory, but will not expand its contents:
romany|mjb $ ls -d $HOME
/u/mjb
romany|mjb $
Some other options...
There are two or three additional switches to the ls command that
should be part of your arsenal.
The -t switch will list all files by modification time, with the most recently
modified listed first, as in the following output of ls -AFxt:
romany|mjb $ ls -AFxt
.sh_history lx* fax/ wisperr.log
copyxp/ open9ktrack.log .exrc listen/
junk.txt Personly.dat listen.tar dwksave/
ezcomp/ bin/ necesito/ smit.log
smit.script log.txt setdwks xerox/
.profile* trash/ Power.dt contest.txt*
alpha_port@ mbox eztree/ .profile.031996*
lamage.cpio.Z mjb.grodin/ justio.c mftime.cbl
acutime.cbl src.224/ amerc/ PERSONALITY
holdit.c STARTUP*
romany|mjb $
In this example, .sh_history is the most recently modified file (and it
always will be if (a) it exists, and (b) history is switched on, because
this file always has whatever command you just issued automatically
added to it). The file lx is the next most recently used; the fax directory follows;
and so on, down to the STARTUP file, which was modified the
longest time ago.
The -u switch uses the last accessed time instead of the last modified
time; combined with -t, it will sort files from most recently used to least recently used.
The result of ls -AFxtu, below, shows that .sh_history is the most recently
accessed, followed by the eztree directory.
romany|mjb $ ls -AFxtu
.sh_history eztree/ amerc/ necesito/
.profile* lx* setdwks alpha_port@
STARTUP* .exrc copyxp/ listen/
mjb.grodin/ fax/ ezcomp/ trash/
xerox/ bin/ src.224/ dwksave/
junk.txt Personly.dat listen.tar Power.dt
log.txt smit.log smit.script contest.txt*
open9ktrack.log .profile.031996* mbox lamage.cpio.Z
wisperr.log justio.c acutime.cbl mftime.cbl
PERSONALITY holdit.c
romany|mjb $
If another file is accessed, it will show up after .sh_history. In the example
below, cat .profile is issued before the ls -AFxtu command. The .profile file
is displayed on the terminal, then the new results for ls -AFxtu indicate
that although .sh_history is still most recently accessed, .profile has moved
into the number two slot.
romany|mjb $ cat .profile
PATH=$PATH:$HOME/bin:.
MAIL=/usr/spool/mail/`logname`
export PATH MAIL
PS1=`uname -n`'\|$PWD \$'
set -o vi
romany|mjb $ ls -AFxtu
.sh_history .profile* eztree/ amerc/
necesito/ lx* setdwks alpha_port@
STARTUP* .exrc copyxp/ listen/
mjb.grodin/ fax/ ezcomp/ trash/
xerox/ bin/ src.224/ dwksave/
junk.txt Personly.dat listen.tar Power.dt
log.txt smit.log smit.script contest.txt*
open9ktrack.log .profile.031996* mbox lamage.cpio.Z
wisperr.log justio.c acutime.cbl mftime.cbl
PERSONALITY holdit.c
romany|mjb $
The third option for checking time values is the -c switch. This uses the
inode modified time. An inode for a file is modified when a file is created,
which is why this is sometimes erroneously called the creation time. The inode
modified time is also reset when the mode of the file is changed, or when a
file is renamed. Because the inode modified time does not always reflect the
creation date and time, this option is less useful, but it's still handy to know.
Another set of useful switches for the ls command are designed to handle files
with unprintable characters in their name. These options are -b and -q.
For this example you need to create a file containing non-printable
characters. In practice, this usually happens due to a typing accident or
through some system error. To create an invalid filename, copy a file to a
filename containing spaces or tabs:
cp junk.txt "ju(tab)nk"
The file junk.txt already exists in the sample directory I've been
working with, and with this command it's copied to a filename that contains a
tab character, by typing the name as "ju(tab)nk".
After this command has been executed, a standard ls command lists this new file
containing invalid characters.
romany|mjb $ ls ju*
ju nk
junk.txt
justio.c
When you see something like this, you know you have a file containing
invalid characters, but what are they? The -q option replaces each unprintable
character with a question mark. This tells you that one character has caused
the gap in the filename of junk.
romany|mjb $ ls -q j*
ju?nk
junk.txt
justio.c
romany|mjb $
It would be safe to guess that this character is a tab, but if you want to
find out for sure which character it is, use the -b switch. The -b switch will replace
unprintable characters with octal representations of the characters. In this
example, the tab is replaced with the octal representation of a tab (\011).
romany|mjb $ ls -b j*
ju\011nk
junk.txt
justio.c
romany|mjb $
The -q switch just tells you that an unprintable character exists in the name.
The other tries to tell you which character (or characters) you're dealing with.
You can remove the bad file by typing rm "ju(tab)nk" .
The example above is fairly easy to see in the directory display. Try
entering the following two commands (using a file that already exists in your
system).
romany|mjb $ cp junk.txt junk
romany|mjb $ cp junk.txt "junk(tab)"
An ls j* command produces a listing that appears to be an impossible
condition -- two files with the same name:
romany|mjb $ ls j*
junk
junk
junk.txt
justio.c
romany|mjb $
An ls -b or ls -q, however, will produce listings that reveal the true state of affairs:
romany|mjb $ ls -b j*
junk
junk\011
junk.txt
justio.c
romany|mjb $ ls -q j*
junk
junk?
junk.txt
justio.c
romany|mjb $
Clean up the example "junk(tab)" file by using the command
romany|mjb $ rm "junk(tab)"
On some systems, a space is considered to be a printing character. When this
is the case, the ls -q and ls -b options won't give you any indication of
the problem, especially when a space appears at the end of a filename, as in
the example below (note the extra space at the end of the second copy
command):
romany|mjb $ copy junk.txt junk
romany|mjb $ copy junk.txt "junk "
romany|mjb $ ls -b j*
junk
junk
junk.txt
justio.c
romany|mjb $ ls -q j*
junk
junk
junk.txt
justio.c
romany|mjb $
You can get around this limitation by using the od utility, which does an octal
display of files. In the example below, the output of ls ju* is piped to the
od utility with switch options (-bc) to display characters in ASCII and in
octal. The octal value for a space is 040, and the 040 shows up as the tenth
character at the end of the second junk entry.
romany|mjb $ ls j*|od -bc
0000000 j u n k \n j u n k \n j u n k .
152 165 156 153 012 152 165 156 153 040 012 152 165 156 153 056
0000020 t x t \n j u s t i o . c \n \0
164 170 164 012 152 165 163 164 151 157 056 143 012 000
romany|mjb $
Contact
us for a free consultation. |