Little big man

Unix Insider –

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:

<font face="Courier">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
</font>

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:

<font face="Courier">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 $
</font>

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:

<font face="Courier">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 $
</font>

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:

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>

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

<font face="Courier">more</font>
and a different picture emerges, as you see in the following two screens of output for ls -AF|more:

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>

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

<font face="Courier">ls -AFx l*
</font>

However, in the result shown below, the output is not at all what I was expecting:

<font face="Courier">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 $
</font>

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:

<font face="Courier">romany|mjb $ ls -AFx l*
listen/       listen.tar      lamage.cpio.Z   log.txt
</font>

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*.

<font face="Courier">romany|mjb $ ls -AFxd l*
listen/         listen.tar      lamage.cpio.Z   log.txt
romany|mjb $
</font>

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:

<font face="Courier">romany|mjb $ ls -AFxd 
./
romany|mjb $
</font>

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

<font face="Courier">ls -d
</font>

has the period (.) added as the default argument and effectively becomes

<font face="Courier">ls -d . 
</font>

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.

<font face="Courier">romany|mjb $ ls -d 
.
romany|mjb $
</font>

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:

<font face="Courier">romany|mjb $ ls -d $HOME
/u/mjb
romany|mjb $
</font>

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:

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>

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:

<font face="Courier">cp junk.txt "ju(tab)nk"
</font>

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.

<font face="Courier">romany|mjb $ ls ju*
ju      nk
junk.txt
justio.c
</font>

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.

<font face="Courier">romany|mjb $ ls -q j*
ju?nk
junk.txt
justio.c  
romany|mjb $
</font>

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).

<font face="Courier">romany|mjb $ ls -b j*
ju\011nk
junk.txt
justio.c  
romany|mjb $
</font>

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

<font face="Courier">rm "ju(tab)nk"</font>
.

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).

<font face="Courier">romany|mjb $ cp junk.txt junk
romany|mjb $ cp junk.txt "junk(tab)" 
</font>

An ls j* command produces a listing that appears to be an impossible condition -- two files with the same name:

<font face="Courier">romany|mjb $ ls j*
junk
junk
junk.txt
justio.c  
romany|mjb $
</font>

An ls -b or ls -q, however, will produce listings that reveal the true state of affairs

:

<font face="Courier">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 $
</font>

Clean up the example "junk(tab)" file by using the command

<font face="Courier">romany|mjb $ rm "junk(tab)" 
</font>

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):

<font face="Courier">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 $
</font>

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.

<font face="Courier">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 $
</font>
Top 10 Hot Internet of Things Startups
You Might Like
Join the discussion
Be the first to comment on this article. Our Commenting Policies