Little big man
Getting to know the small but popular ls command
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)
y 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
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
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 $
About the author
Mo Budlong, president of King Computer Services Inc., specializes in Unix and client/server consulting and training, and currently publishes the COBOL Just In Time Course, a crash course for the Year 2000 problem, as well as COBOL Dates and the Year 2000, which offers date solutions. Reach Mo at firstname.lastname@example.org.
If you have technical problems with this magazine, contact email@example.com