comp.unix.shell FAQ - Answers to Frequently Asked Questions

From: Joe Halpin (j.p.h_at_comcast.net)
Date: 08/03/03


Date: Sun, 03 Aug 2003 18:38:50 GMT

Archive-name: comp-unix-shell/cus-faq
Posting-Frequency: monthly
Version: $Id: cus-faq,v 1.6 2003/08/03 16:40:06 jhalpin Exp jhalpin $
Copyright: (c) 2003 Joe Halpin
Maintainer: Joe Halpin <j.p.h@comcast.net>

This FAQ list contains the answers to some Frequently Asked Questions
often seen in comp.unix.shell. It spells "unix" in lower case letters
to avoid arguments about whether or not Linux, FreeBSD, etc are
unix. That's not the point of this FAQ, and I'm ignoring the issue.

Suggestions, complaints, et al, should be sent to the maintainer at
j.p.h@comcast.net or posted to comp.unix.shell.

There are two levels of questions about shells.

One is the use of the shell itself as an interface to the operating
system. For example, "how do I run a program in the background, and go
on with other things?". Or "how do I setup environmental variables
when I log in?".

The other level is how to write shell scripts. This often involves
having the shell execute unix utilities to perform part of the work
the shell script needs to accomplish, and requires knowledge of these
utilities, which isn't nominally in the scope of shell
programming. However, unless the question involves something other
than standard unix utilities, it should be included in this FAQ.

Standard unix utilities are defined by either POSIX or the Single Unix
Specification. These are now joined and are normally abbreviated as
"POSIX/SUS". This specification can be found at

http://www.opengroup.org/onlinepubs/007904975/toc.htm

The man pages found on that web page define standard behavior for any
given utility (or the shell itself). However, you should also check
the man page on your system for any utility or shell you need to
use. There isn't always a perfect correspondence between the standard
and a particular implementation (in fact, I'm not sure there's any
case in which they perfectly correspond).

Other good web sites that provide information about shells and shell
programming (including OS utilities) include:

http://www.shelldorado.com/
http://www.faqs.org/faqs/by-newsgroup/comp/comp.unix.shell.html

The predictable legal stuff
---------------------------
The answers given in this FAQ list are provided with the best
intentions, but they may not be accurate for any particular
shell/os. They may be completely wrong for any shell/os. If you don't
test the answers, that's a bug in your procedures.

There are no guarantees for the answers or recommendations given in
this document. In fact, I don't even claim to have tested any or all
of them myself. Many of the answers here have been contributed by one
or more regular participants in the newsgroup, who I believe to be
competent (certainly more competent than I am), but THERE ARE NO
GUARANTEES.

Did I really need to make that all uppercase? Hopefully not, but there
are a lot of lawyers around with too much time on their hands, so I
want to make it clear that THERE ARE NO GUARANTEES about the accuracy
of answers in this FAQ list. This is, hopefully, an aid to people
trying to learn shell programming, but it is NOT a supported
product. You have to figure out for yourself whether or not the
answers here work for what you're trying to do.

Under no circumstances will the maintainer of this FAQ list, or any
contributors to it, be held liable for any mistakes in this
document. If the answers work for you, well and good. If not, please
tell me and I'll modify them appropriately so that this will be more
useful.

If you don't agree to that, don't read any farther than this. Reading
beyond this point indicates your agreement.

If you do test the answers and find a problem, please send email to
the maintainer (see above), so it can be corrected, or (preferably)
post a question to the newsgroup so it can be discussed and corrected
if there's a problem.

A number of people have contributed to this FAQ, knowingly or
unknowingly. Some of the answers were taken from previous postings in
the group, and other people contributed questions and answers
directly to the maintainer, which you are welcome to do as well.

Among the contributors is Heiner Stevens, who also provided the
momentum to get this FAQ list started. He maintains a web site about
shell programming that has a lot of good stuff in it.

http://www.shelldorado.com/

======================================================================

CONTENTS:

0. Glossary
    Google
    POSIX/SUS ("the standard")
    UUOC
    dotfile
    portable
    race condition
    shebang
    sh
    csh
    pdksh
    ksh88
    ksh93
    tcsh
    zsh
    rc
    es
    top-posting

0a. Notes about using echo
1. How can I send e-mails with attached files?
2. How can I generate random numbers in shell scripts?
3. How can I automatically transfer files using FTP with error checking?
4. How can I remove whitespace characters within file names?
5. How can I automate a telnet session?
6. How do I do date arithmetic?
7. Why did someone tell me to RTFM?
9. How do I create a lock file?
10. How can I convert DOS text files to unix, and vice versa?
11. How can a shell prompt be set up to change the title of xterm?
12. How do I batch an FTP download/upload?
13. How do I get the exit code of cmd1 in cmd1|cmd2
14. Why do I get "script.sh: not found"
15. Why doesn't echo do what I want?
16. How do I loop through files with spaces in their name?
17. how do I change my login shell?
18. When should I use a shell instead of perl/python/ruby/tcl...
19. Why shouldn't I use csh?
20. How do I reverse a file?
21. How do I remove last 10 lines?
22. how do I get file size, or file modification time?
23. How do I get a process id given a process name? Or, how do I find
    out if a process is still running, given a process ID?
24. How do I get a script to update my current environment?
25. how do I rename *.foo to *.bar?
26. How do I use shell variables in awk scripts
27. How do I input the user with a timeout?
28. How do I get one character input from the user?
29. why isn't my .profile read?
30. why do I get "[5" not found in "[$1 -eq 2]"?
31. How do I exactly display the content of $var (with a \n appended).
32. How do I exactly display the content of $var (without a \n
    appended).

Appendix A: Some example scripts
Appendix B: References. These correspond with numbers in square
            brackets (e.g. [1]) which may appear in the text.

======================================================================

ANSWERS

0. Glossary

   Google
   -------------------------------

      Google is one of the search engines on the Internet. It took
      over dejanews some years ago, and now is the standard reference
      when directing someone to a past thread one some topic. This is
      a very good place to start when researching a question about
      shell programming (and just about anything else).

        http://groups.google.com/advanced_group_search

   POSIX/SUS ("the standard")
   -------------------------------

      POSIX (Portable Operating System Interface) and SUS (Single Unix
      Specification) have been joined into one standard. This is what
      people usually mean when they refer to "the standard" in
      discussions about unix. When people in this group refer to the
      POSIX shell, they are talking about the shell prescribed by this
      specification. You can find this standard at

        http://www.opengroup.org/onlinepubs/007904975/toc.htm

   UUOC
   -------------------------------
   
      This is short for "Useless use of cat". It's used to point out
      that some example script has used cat when it could have used
      redirection instead. It's more efficient to redirect input than
      it is to spawn a process to run cat. For example

        $ cat file | tr -d 'xyz'

      runs two processes, one for cat and one for tr. This is less
      efficient than

        $ tr -d 'xyz' < file

      In general, "cat file | somecommand" can be more efficiently
      replaced by "somecommand < file".

   dotfile
   -------------------------------

      This refers to a file which starts with '.' (at dot). These
      files are not shown in directory listings without the -a
      option to ls. Often they are configuration files, subdirectories
      used by applications to store configuration files, NFS swap
      files, et al.

   portable
   -------------------------------

      The word "portable" means different things to different people,
      in different situations, which is to say, there isn't one
      definition of "portable".

      At one extreme, a portable script is one which will work under
      any shell, on any operating system. At this end of the spectrum,
      there is no such thing as a portable shell script (some
      operating systems don't even have shells). If we confine the
      operating system to unix (which would make sense since this is
      comp.unix.shell), the only truly portable scripts are those
      which make no use of built-in shell facilities or syntax, but
      which only call external utilities. For example

        echo Hello World

      would probably qualify. However, that doesn't do anyone much
      good.

      Given that there are probably few (if any) scripts which have to
      meet such a standard, a more frequent use of the word "portable"
      indicates the degree to which a script will run under different
      shells and/or different operating environments.

      For example, if you're writing an installation script for an
      application, and the platforms on which that application runs
      are defined, then the problem is pretty well bounded. The choice
      of shell is one which is available on all required platforms,
      and the syntax to be used is the smallest subset of all the
      variants of that shell on the target platforms.

      The degree to which your shell script needs to be portable has
      to be determined by you, or the requirements you've been given
      for the script.

   race condition
   -------------------------------

      This is a situation in which two entities (processes, threads,
      etc) are trying to access a shared resource, or perform the same
      action, and the result depends on the order of execution of the
      two entities.

   shebang
   -------------------------------

      This is the first line of a shell script, which indicates to the
      operating system which interpreter (shell) it should invoke to
      interpret the script. It has the form

      #!/path/to/shell [ argument ]

      where /path/to/shell might be /bin/sh, /usr/local/bin/bash, etc.

      This line is only interpreted by the operating system. That is,
      if a shell script (test.sh) is executable and run from the
      command line by typing its name.

      If it's run by typing

      $ sh test.sh

      then sh is run with the argument "test.sh". It then interprets
      test.sh. For sh, the shebang line is simply a comment, and is
      ignored.

   sh
   csh
   pdksh
   ksh88
   ksh93
   tcsh
   zsh
   rc
   es
   -------------------------------

      These (and more) are names of shells which are referenced in the
      group. A comparison of some of these is available at

        http://www.faqs.org/faqs/unix-faq/shell/shell-differences/

      However, it does not make specific the differences between
      ksh88, ksh93 and pdksh, which are not entirely compatible.

   top-posting
   -------------------------------

   This is usually something done by people running Windows software,
   which many times defaults to the Wrong Thing.

   Top posting means that your response appears above what you're
   responding to. For example, if you just came into the following
   thread, you'd likely not find it very intuitive.

     Subject: Re: help
     From: top-poster <top-poster@someplace.else>
     Date: Thu, 31 Jul 2003 22:01:41 GMT
     Newsgroups: comp.unix.shell
     Organization: Top-posters United

     Wait a minute. If your computer isn't on, how are you posting this?

     clueless <clueless@someplace.somewhere> writes:

> I did. What do I do now?
>
> top-poster <top-poster@someplace.else> writes:
>
> > Push the button. If that doesn't work, go to
> > http://www.microsoft.com
> >
> > clueless <clueless@someplace.somewhere> writes:
> >
> > > How do I turn my computer on?
> > >
> > > --
> > > This is my tagline. It's not much, but it's mine
> > >
> >
> > --
> > This is my tagline, and it's the best there is.
>
> --
> This is my tagline. It's not much, but it's mine
  
     --
     This is my tagline, and it's the best there is.

   As you can see, top-posting rapidly turns into a labyrinth, which
   is very difficult to follow, because anyone coming in on it later
   has to read backwards to figure out what's going on (and think what
   that could mean with multi-line signatures that don't get edited).

   The solution? Answer inline, and edit what came before in a
   sensible fashion. Post your responses after what you're responding
   to, just like when you're talking to someone in the real world. For
   example, the above would be something like this inline, with
   reasonable editing:

     Subject: Re: help
     From: reformed-top-poster <reformed-top-poster@someplace.else>
     Date: Thu, 31 Jul 2003 22:01:41 GMT
     Newsgroups: comp.unix.shell
     Organization: Reformed Top-Posters

     clueless <clueless@someplace.somewhere> writes:
> top-poster <top-poster@someplace.else> writes:
> > clueless <clueless@someplace.somewhere> writes:
> >
> > > How do I turn my computer on?
> >
> > Push the button. If that doesn't work, go to
> > http://www.microsoft.com
>
> I did. What do I do now?

     Wait a minute. If your computer isn't on, how are you posting this?

     reformed-top-poster
     --
     This is my tagline, and it's the best there is.

   Which, as you can see, is much more understandable.

   People here aren't Stalinists about it, but after a while, a thread
   is more likely to just die rather than result in an answer if
   top-posting continues, simply because people get tired of putting up
   with it. There are quite a few articles posted to this group at
   times. If you make yours easily understandable you have a better
   chance of not getting consigned to the bit-bucket by experienced
   contributors.

   So please, consider talking to us the same way you talk to real
   people, in sequential order. After all, you don't normally respond
   to a question before it's asked, right? For people coming into a
   thread in the middle, that kind of approach would probably be so
   chaotic that they'll just ignore the thread, and you might not get
   the answer you need.

   For more details consider looking at

      http://www.uwasa.fi/~ts/http/quote.html
      http://members.fortunecity.com/nnqweb/
      http://www.math.fu-berlin.de/~guckes/message/editing.html
      
======================================================================

0a. Notes about using echo

   This isn't really a FAQ, but discussions about using echo come up
   often enough that it seems reasonable to have something about it in
   the FAQ list.

   The echo command is not consistent in the handling of its arguments
   from implementation to implementation. Sometimes a string with
   backslash quoted characters will be interpreted in one way, and
   sometimes another. For example

   s="a string with\na newline and\ta tab"

   Following are some results with various shells:

   bash:
   ------
   $ echo $s
   a string with\na newline and\ta tab
   
   $ echo -e $s
   a string with
   a newline and a tab

   pdksh:
   -------
   $ echo $s
   a string with
   a newline and a tab

   $ echo -e $s
   $ echo $s
   a string with
   a newline and a tab

   ksh88:
   --------
   $ echo $s
   a string with
   a newline and a tab

   $ echo -e $s
   -e a string with
   a newline and a tab

   ksh93:
   -------
   $ echo $s
   a string with\na newline and\ta tab

   $ echo -e $s
   -e a string with\na newline and\ta tab

   Note that ksh93 makes the handling of arguments system dependent
   when they contain '\', and/or the first argument begins with '-'.

   http://www.cs.princeton.edu/~jlk/kornshell/doc/man93.html

   POSIX does not allow the -e option. It also makes the result of
   using -n or any string with '\' in it implememtation-defined.
   However, on XSI-conforming systems, it disallows options, and
   defines the use of backslash-quoted characters.

   In general, the behavior of echo is system and/or shell dependent
   if its arguments contain a backslash, or its first argument is -n
   or -e.

   The biggest problem with echo is when using it to output strings
   that the script got externally (e.g. use input, or reading from a
   file). These strings may have '\' characters in them for
   example. In this case, results may not be what you expect.

   print is a widely available option (although not in bash or sh), as
   is printf(1). Additionally, a here document will give predictable
   results in that it will not expand escape sequences.

   cat <<EOF
   $s
   EOF

   produces

   a string with\na newline and\ta tab

   So consider not using echo unless you are sure what will happen,
   given the shell you're using.

======================================================================

1. How can I send e-mails with attached files?

   a. Use uuencode
   
      This is the simplest way to do this. For example

      $ uuencode surfing.jpeg surfing.jpeg | mail sylvia@home.com

      To send regular text as well

      $ (cat mailtext; uuencode surfing.jpeg surfing.jpeg) |
        mail sylvia@home.com

   b. Use MIME

      $ metasend -b -t john@friends.org -s "Hear our son!" \
            -m audio/basic -f crying.au

      These examples are taken from
      http://www.shelldorado.com/articles/mailattachments.html which
      goes into much more detail about this.

======================================================================

2. How can I generate random numbers in shell scripts?

   This depends on the shell, and the facilities available from the
   OS.

   a. Some shells have a variable called RANDOM, which evaluates to a
      different value every time you dereference it. If your shell has
      this variable,

        $ number=$RANDOM will produce a random number.

   b. Some systems have a /dev/urandom device, which generates a
      stream of bits. This can be accessed using the dd(1) utility. An
      example of this (from a more extensive discussion of different
      techniques at http://www.shelldorado.com/scripts/cmds/rand)

        n=`dd if=/dev/urandom bs=1 count=4 2>/dev/null | od -t u4 | \
        awk 'NR==1 {print $2}'`

   c. Use a utility such as awk(1), which has random number generation
      included. This approach is the most portable between shells.

        awk 'BEGIN {srand();print rand()}'

      However, note that if you call this line more than once within
      the same second, you'll get the same number you did the previous
      time.

======================================================================

3. How can I automatically transfer files using FTP with error
   checking?

    First, there are tools to do that: curl, wget, lftp, ncftp. But, they
    are generally not part of the base system (you need to install them).
    
    zsh (version 4 and above) provides a FTP facility, see
    "info -f zsh -n 'zsh/zftp Module'"
    
    #! /usr/bin/zsh
    zftp open host user passwd || exit
    zftp get /remote/file > /local/file; r=$?
    zftp close && exit r
    
    With your system "ftp" command, two ways:
    
    1- using "ftp -n". Without the -n option, ftp expects user interaction
    to enter the password, so you'd need to use "expect". With "-n", you
    provide the user and password as any other FTP command.
    
    #! /bin/sh
    ftp -n << EOF
    open ftp.domain.org
    user anonymous ${LOGNAME:-`who am i`}@
    binary
    get /remote/file /local/file
    bye
    EOF
    
    The error checking can't be made correctly (if "open" fails, the "user"
    command will be still sent even if it shouldn't).
    
    2- using ~/.netrc
    
    If you put:
    
    <<
    machine ftp.domain.org
    login mylogin
    password mypasswd
    macdef init
      binary
      get /remote/file /local/file
      bye
    
    
>>
    
    (with the trailing empty line) in your ~/.netrc (ensure it's not world
    readable) and then run "ftp ftp.domain.org", ftp will find the matching
    "machine" entry in your ~/.netrc and use the parameters provided there
    to make the ftp transaction.
    
    Those work at least on Linux, FreeBSD, Solaris, HPUX

======================================================================

4. How can I remove whitespace characters within file names?

   File names in unix can contain all kinds of whitespace characters,
   not just spaces. The following examples only work with spaces,
   adjust accordingly.

   a. Use the substitution capabilities of awk, sed, et al.

        f=`printf '%s\n' "$filename" | sed 's/ /_/g'`

        f=`printf '%s\n' "$filename" | awk '{gsub(" ","_");print $0}'`

        f=`printf '%s\n' "$filename" | tr ' ' _`

      Add characters to the tr command line as needed (see the man
      page for tr to find out the available escape sequences).

      Additionally (although not exactly a one-liner)

        f=`tr ' ' _ <<EOF
        $filename
        EOF
        `

      See section 0a "Notes about using echo" for why echo is not used
      here.

   b. Use the substitution capabilities of the shell if it has
      them. Check the man page for your shell (probably under a
      section named something like "Parameter expansion") to see. The
      following works with bash, ksh93 and zsh (at least).

      f=${filename// /_}

======================================================================

5. How can I automate a telnet session?

   This is outside the realm of shell programing, per se. You need
   a more special purpose scripting language such as expect. See
   http://expect.nist.gov/

   Perl scripts can also do this with the Telnet module from CPAN.

======================================================================

6. How do I do date arithmetic?

   This depends on exactly what you have in mind.

   a. Finding yesterday's date

      if you want to determine whether or not one file is older than
      another, you can (with bash, pdksh, ksh93) do

        $ [[ file1 -ot file2 ]] && echo file1 is older

      or you can use find to search a directory tree for files that
      are newer/older than some file:

        $ find . -name '*.c' -cnewer test.c

      The GNU version of date also has some nice features in this
      respect. For example

        To find yesterday's date

          $ date --date yesterday

        To find tomorrow's date

          $ date --date tomorrow

         See the man page for GNU date for other options. It can also
         provide dates more than one day in the past/future.

       However, daylight saving time can make this inaccurate if you
       do it at the right (wrong?) time of the year.

   b. Finding elapsed time

      If you want to find elapsed time, perhaps because you want to
      know when some operation has timed out, some shells (bash, ksh
      [,??]) have a SECONDS variable which tell how many seconds have
      elapsed since the invocation of the shell, or since the last
      time it was set.

   c. Determining leap year

      A leap year is defined as a year which is evenly divisible by 4,
      however, if it's also evenly divisible by 100 then it's not a
      leap year unless it's also evenly divisible by 400. It gets
      worse than that, actually, but this is as far as I go :-)

      One possibility for a ksh function to do this is

        isleap()
        {
          y=$1
          four=$(( $y % 4 ))
          hundred=$(( $y % 100 ))
          fourhundred=$(( $y % 400 ))
          if [ $four -eq 0 ];then
            if [ $hundred -eq 0 ];then
              if [ $fourhundred -eq 0 ];then
                echo leap year
              else
                echo not a leap year
              fi
            else
              echo leap year
           fi
         else
           echo not a leap year
         fi
        }

   d. Determining the day of the week for a given date.

      This algorithm is known as Zeller's congruence. An explanation
      of it is available from the Dictionary of Algorithms and Data
      Structures web page at NIST:

        http://www.nist.gov/dads/

      Also, a fuller explanation is available at

        http://www.merlyn.demon.co.uk/zeller-c.htm#ZC

      An example in C, with a short explanation, is given at

        http://wwwcdf.pd.infn.it/MLO/Calendars/Notes.html#zeller

      A shell (ksh93) implementation of a homework assignment (given
      for illustration only - don't turn this in as yours - you might
      be sorry if it's wrong :-)

        dayofweek()
        {
          # Implementation of a homework assignment given at
          # http://carbon.cudenver.edu/~traup/fa02/lec/hw3.html
          #
          # call with day: 1 - 31
          # month: March = 1, Jan & Feb are months 11 and
          # 12 of the previous year.
          # year: The year of the century
          # c: The previous century
          #
          # For example, for July 4, 1989,
          # m = 5, d = 4, y = 89, and c = 19,
          # while for January 25, 1989,
          # m = 11, d = 25, y = 88, and c = 19.
          #
          # The output is the day of the week with Sunday = 0,
          # Monday = 1, etc.

          d=$1
          m=$2
          y=$3
          c=$4

          A=$(( ($m * 13 - 1) / 5 ))
          B=$(( $y / 4 ))
          C=$(( $c / 4 ))
          D=$(( $A + $B + $C + $d + $y - ($c * 2) ))
          echo $(( $D % 7 ))
        }

   e. Arbitrary date arithmetic

      To do arbitrary date calculations is more complicated. One
      possibility is to call an external utility, or a program in
      another scripting language, which has this built in. For
      example, perl has wrappers for the unix time functions built in,
      so it can provide some relief in this regard. C programs can also
      be easily written to do date arithmetic (see the examples
      section). One thing to keep in mind, however, is that unix time
      functions are, strictly speaking, limited to the range of time
      between January 1 1970 at midnight, and January 19, 2038 at
      3:14:07. C/Perl programs which calculate dates outside this
      range might work, or they might not, that would depend on the
      implementation.

      To do arbitrary date arithmetic in the shell itself is also
      possible. An article graciously provided on the web by SysAdmin
      magazine describes one way to do this.

        http://www.samag.com/documents/s=8284/sam0307b/0307b.htm

      Another possibility is given in the examples section, from

        http://groups.google.com/groups?selm=n6g01kfj7n.fsf%40ogion.it.jyu.fi

======================================================================

7. Why did someone tell me to RTFM?

   Because you didn't :-)

   RTFM is part of Usenet lingo, and means "Read The F-ing Manual".
   Generally people say this when someone asks a question that is
   asked so often, and is answered plainly in some relevant man page,
   that they're tired of seeing it asked.

   So RTFM, and the FAQs first before asking. Also, if you're new to
   the group, search Google Groups

   http://groups.google.com/advanced_group_search

   before asking questions. And please don't post your homework
   questions to the group unless you've tried to figure them out, and
   have some specific questions. People will generally be happy to
   help you with your homework if you post what you've got and ask
   specific questions.

======================================================================

9. How do I create a lock file?

   Very carefully :-)

   The scheduler can stop one process in the middle of a non-atomic
   operation, and run another one, which wants to perform the same
   operation. The second one, having a full timeslice, might finish
   the operation. When control returns to the first process, confusion
   will reign.

   The trick is to do something atomic, so that this won't
   happen. There are a couple ways to do this. One is to create a
   directory instead of a file, the other is to create a symbolic
   link. Both operations are defined to be atomic by POSIX/SUS, by
   virtue of the fact that they both require calls to the
   corresponding system calls, which are atomic.

   Beware of trying to create ANY kind of lock file on an NFS
   partition. NFS pretty much eliminates anything like atomicity. If
   you're going to create a lock file, make sure you're doing it on a
   local partition, such as /tmp.

   Netscape/Mozilla uses the symbolic link method for its lockfile (in
   spite of the fact that it creates it in the user's home directory,
   which may be NFS mounted). When it starts up it creates a file
   named for the IP address of the machine it's running on, and the
   pid of the creating process. Then it tries to create a symbolic
   link named "lock", which points to that file. If this symlink
   already exists, link(2) will return an error. In a script this
   would work something like

   touch /tmp/xxx
   ln -s /tmp/xxx /tmp/lockfile 2>/dev/null
   ret=$?
   rm /tmp/xxx
   if [ $ret -ne 0 ];then
     echo lockfile already exists
     exit 1
   else
     echo success
   fi

   If you have procmail installed, another possiblity is the
   lockfile(1) command that comes with it.

======================================================================

10. How can I convert DOS text files to unix, and vice versa?

    Unix text files consist of lines delimited by an LF ("line-feed")
    character (ASCII 10). DOS uses the two characters CR LF ("carriage
    return", "line feed"; ASCII 13, 10) for the same purpose.

    To convert a DOS text into unix text format, the CR characters
    (control-M) at the end of a line have to be removed. To create a
    DOS text file, the CR character should be added.

    A couple ways to remove CR characters:

      sed 's/^M$//' dos.txt > unix.txt

      tr -d '\r' < dosfile > unixfile

    To add them:

      sed 's/$/^M/' unix.txt > dos.txt

    Note that "^M" in this case is an embedded control character, (CR,
    ASCII 13). Many shells allow embedding control characters by
    entering ^V first (control-V), resulting in the sequence

        ^V^M

    for entering "^M".

    However, zsh, bash or ksh93 allow for:

      sed $'s/$/\r/'

    There is one special case to be considered: DOS text files
    sometimes contain an explicit end-of-file character ^Z (ASCII 26,
    or octal 32), which has no correspondent character for unix text
    files, where the end-of-file condition is determined
    implicitly. To remove that as well as the CR characters:
 
      tr -d '\r\032' < dosfile > unixfile

    since tr understands \r to mean ^M and \0octal for anything (^Z =
    ASCII 26 = octal 32); tr -d '\015\032' works, too. It's been
    required by POSIX since 1992, but it was pretty much existing
    practice even earlier (I'm told that SunOS 4.1's tr is an
    exception to this, and doesn't understand that notation).

    Note that sed does not understand that notation, but awk does, and
    one simple way to do the opposite conversion is

      $ awk '{print}' ORS='\r\n' unixfile > dosfile
      $ print "\032" >> dosfile

    This assume a not-quite-ancient awk, in practice anything
    but Solaris /bin/awk (use nawk or /usr/xpg4/bin/awk in Solaris).

    Finally, your system may come with utilities named something like
    dos2unix and unix2dos.

======================================================================

11. How can a shell prompt be set up to change the title of xterm?

    http://cns.georgetown.edu/~ric/howto/Xterm-Title/Xterm-Title-singlepage.html
    Gives escape sequences for xterm. For example, to change the name
    of the current window to "XXX" (in bash), do

      $ echo -en "\033]2;XXX\007"

    See also "Why doesn't echo do what I want?"

======================================================================

12. How do I batch a FTP download/upload?

    The best way to handle this is with ncftpput and ncftpget which
    are part of the ncftp program. ncftpput -u username -p password
    somewebsite.com /pics *jpg The above usage of the username and
    password is not recomend though as it will be seen by anyone using
    "ps" while the script is running. ncftp has a way to handle that
    as well. Just create a file with the information in the following
    formate:

              host somewebsite.com
              user username
              pass password

    Then just use the -f option on the ncftp program:
    ncftpput -f /home/username/somefile somewebsite.com /pics *jpg

    ncftp can be found at <url:http://freshmeat.net/projects/ncftp/>
    
    If you want to do this interactively, there's no need to keep the
    password in a file. For example, if you're building a program on
    one machine, but testing it on another, and you have to keep
    ftp'ing the files, you can cut down on typing by doing something
    like

    #!/bin/ksh
    ftp -n $remote_host <<EOF
    user <username>
    cd <path to build directory>
    prompt
    bin
    mget "$@"
    bye
    EOF

    The ftp program will automatically ask you for the password, then
    do the rest for you.

======================================================================

13. How do I get the exit code of cmd1 in cmd1|cmd2

    First, note that cmd1 exit code could be non-zero and still don't
    mean an error. This happens for instance in

    cmd | head -1

    you might observe a 141 (or 269 with ksh93) exit status of cmd1,
    but it's because cmd was interrupted by a SIGPIPE signal when
    "head -1" terminated after having read one line.

    To know the exit status of the elements of a pipeline
    cmd1 | cmd2 | cmd3

    a. with zsh:

       The exit codes are provided in the pipestatus special array.
       cmd1 exit code is in $pipestatus[1], cmd3 exit code in
       $pipestatus[3], so that $? is always the same as
       $pipestatus[-1].

    b. with bash:

       The exit codes are provided in the PIPESTATUS special array.
       cmd1 exit code is in ${PIPESTATUS[0]}, cmd3 exit code in
       ${PIPESTATUS[2]}, so that $? is always the same as
       ${PIPESTATUS: -1}.

    c. with any other Bourne like shells

       You need to use a trick to pass the exit codes to the main
       shell. You can do it using a pipe(2). Instead of running
       "cmd1", you run "cmd1; echo $?" and make sure $? makes it way
       to the shell.

       exec 3>&1
       eval `
         # now, inside the `...`, fd4 goes to the pipe
         # whose other end is read and passed to eval;
         # fd1 is the normal standard output preserved
         # the line before with exec 3>&1
         exec 4>&1 >&3 3>&-
         {
           cmd1 4>&-; echo "ec1=$?;" >&4
         } | {
           cmd2 4>&-; echo "ec2=$?;" >&4
         } | cmd3
         echo "ec3=$?;" >&4
       `

    d. with a POSIX shell

       You can use this function to make it easier:

       run() {
         j=1
         while eval "\${pipestatus_$j+:} false"; do
           unset pipestatus_$j
           j=$(($j+1))
         done
         j=1 com= k=1 l=
         for a; do
           if [ "x$a" = 'x|' ]; then
             com="$com { $l "'3>&-
                         echo "pipestatus_'$j'=$?" >&3
                       } 4>&- |'
             j=$(($j+1)) l=
           else
             l="$l \"\$$k\""
           fi
           k=$(($k+1))
         done
         com="$com $l"' 3>&- >&4 4>&-
                    echo "pipestatus_'$j'=$?"'
         exec 4>&1
         eval "$(exec 3>&1; eval "$com")"
         exec 4>&-
         j=1
         while eval "\${pipestatus_$j+:} false"; do
           eval "[ \$pipestatus_$j -eq 0 ]" || return 1
           j=$(($j+1))
         done
         return 0
       }
       
       use it as:
       
       run cmd1 \| cmd2 \| cmd3
       exit codes are in $pipestatus_1, $pipestatus_2, $pipestatus_3

======================================================================

14. Why do I get "script.sh: not found"

    a. While script starts with "#!/bin/sh" (^M issue)

       That's the kind of error that occurs when you transfer a file
       by FTP from a MS Windows machine. On those systems, the line
       separator is the CRLF sequence, while on unix the line
       separator is LF alone, CR being just another ordinary character
       (the problem is that it is an invisible one on your terminal
       (where it actually moves the cursor to the beginning of the
       line) or in most text editors or pagers).

       So, if a MSDOS line is "#!/bin/sh", when on a Unix system, it
       becomes "#!/bin/sh<CR>" (other names for <CR> are \r, \015, ^M,
       <Ctrl-M>).

       So, if you run the file as a script, the system will look in
       /bin for an interpreter named "sh<CR>", and report it doesn't
       exist.

       $ sed 'l;d;q' < script.sh
       #!/bin/sh\r$

       shows you the problem ($ marks the end of line, \r is the CR
       character).

    b. PATH issue

       Sometimes a shell is installed someplace other than /bin or
       /usr/bin. For example, a shell which was not part of the OS
       installation might be installed into /usr/local/bin. If the
       script was written on a machine which had ksh located in
       /usr/bin, but was run on a machine where ksh was located in
       /usr/local/bin, the shebang line would not resolve correctly.

       This is unlikely to occur when using sh. However, if the shell
       is bash, zsh, et al, it might be installed in different places
       on different machines.

       One way around this is to use the env command in the shebang
       line. So instead of

       #!/bin/sh

       use

       #!/usr/bin/env sh

       Of course, env might itself live in some other directory than
       /usr/bin, but it's not likely.

======================================================================

15. Why doesn't echo do what I want?

    See also section 0a "Notes about using echo"

    The echo command is not consistent from shell to shell. For
    example, some shells (bash, pdksh [,?]) use the following
    arguments

      -n suppress newline at the end of argument list
      -e interpret backslash-escaped characters
      -E disable interpretation of backslash-escaped characters, even
         on systems where interpretation is the default.

    However, pdksh also allows using \c to disable a newline at the
    end of the argument list.

    POSIX only allows \c to be used to suppress newlines, and doesn't
    accept any of the above arguments.

    ksh88 and ksh93 leave the interpretation of backslash-escaped
    characters up to the implementation.

    [descriptions of behavior of other shells welcome]

    In short, you have to know how echo works in any environment you
    choose to use it in, and its use can therefore be problemmatic. If
    available, print(1) or printf(1) would be better.

======================================================================

16. How do I loop through files with spaces in their name?

    So, you're going to loop through a list of files? How is this list
    stored? If it's stored as text, there probably was already an
    assumption about the characters allowed in a filename. Every
    character except '\0' (NUL) is allowed in a file path on Unix. So
    the only way to store a list of file names in a file is to
    separate them by a '\0' character (if you don't use a quoting
    mechanism as for xargs input).

    Unfortunately most shells (except zsh) and most standard unix text
    utilities (except GNU ones) can't cope with "\0"
    characters. Moreover, many tools, like "ls", "find", "grep -l"
    output a \n separated list of files. So, if you want to
    postprocess this output, the simpler is to assume that the
    filenames don't contain newline characters (but beware that once
    you make that assumption, you can't pretend anymore your code is
    reliable (and thus can't be exploited)).

    So, if you've got a newline separated list of files in a
    list.txt file, Here are two ways to process it:

    1-

    while IFS= read -r file <&3; do
      something with "$file" # be sure to quote "$file"
    done 3< list.txt
    (if your read doesn't have the "-r" option, either make another
    assumption that filenames don't contain backslashes, or use:

    exec 3<&0
    sed 's/\\/&&/g' < list.txt |
    while IFS= read file; do
      something with "$file" <&3 3<&-
    done
    )

    2-

    IFS="
    " # set the internal field separator to the newline character
      # instead of the default "<space><tab><NL>".
    
    set -f # disable filename generation (or make the assumption that
           # filenames don't contain *, [ or ? characters (maybe more
           # depending on your shell)).
    
    for file in $(cat < list.txt); do
      something with "$file" # it's less a problem if you forget to
                             # quote $file here.
    done
    
    Now, beware that there are things you can do before building
    this list.txt. There are other ways to store filenames. For
    instance, you have the positionnal parameters.
    
    with:
    set -- ./*.txt
    
    you have the list of txt files in the current directory, and no
    problem with weird characters. Looping through them is just a
    matter of:
    
    for file
    do something with "$file"
    done
    
    You can also escape the separator. For instance, with
    
    find . -exec sh -c 'printf %s\\n "$1" | sed -n '"':1
      \$!{N;b1
      }
      s/|/|p/g;s/\n/|n/g;p'" '{}' '{}' \;
      
    instead of
    
    find . -print
    
    you have the same list of files except that the \n in filenames
    are changed to "|n" and the "|" to "|p". So that you're sure
    there's one filename per line and you have to convert back "|n"
    to "\n" and "|p" to "|" before refering to the file.

======================================================================

17. how do I change my login shell?

    See http://www.faqs.org/faqs/unix-faq/shell/shell-differences

    Unless you have a very good reason to do so, do not change root's
    default login shell. By "default login shell" is meant the shell
    recorded in /etc/passwd. Note that "I login as root but don't like
    the default shell" isn't a good reason.

    The default shell for root is one which will work in single user
    mode, when only the root partition is mounted. This is one of the
    contexts root works in, and the default shell must accomodate
    this. So if you change it to a dynamically linked shell which
    depends on libraries that are not in the root partition, you're
    asking for trouble.

    The safest way of changing root's shell is to login as root and
    then

      # SHELL=/preferred/shell; export SHELL
      # exec <your preferred shell with login flag>

      e.g.

      # SHELL=/usr/bin/ksh; export SHELL
      # exec $SHELL -l

    Another possibility is to add something to root's .profile or
    .login which checks to see if the preferred shell is runnable, and
    then execs it. This is more complicated and has more pitfalls than
    simply typing "exec <shell>" when you login though. For example,
    one of the libraries that the desired shell relies on might have
    been mangled, etc. One suggestion that has been made is

      if [ -x /usr/bin/ksh ]; then
        SHELL=/usr/bin/ksh; export SHELL
        ENV=/root/.kshrc; export ENV
        /usr/bin/ksh -l && exit
      fi

    A safer way is to try to run a command with the preferred shell
    before you try to exec it. This will lessen the possibility that
    the shell or one of the libraries it depends on has been
    corrupted, or that one of the libraries it depends on is not in
    the available mounted partitions.

      if [ -x /usr/bin/ksh ]; then
        /usr/bin/ksh -c echo >/dev/null 2>&1
        if [ $? -eq 0 ];then
           SHELL=/usr/bin/ksh; export SHELL
           ENV=/root/.kshrc; export ENV
           /usr/bin/ksh -l && exit
        fi
      fi
    
======================================================================

18. When should I use a shell instead of perl/python/ruby/tcl...

    a. Portability

       In many cases it can't be assumed that perl/python/etc are
       installed on the target machine. Many customer sites do not
       allow installation of such things. In cases like this, writing
       a shell script is more likely to be successful. In the extreme,
       writing a pure Bourne shell script is most likely to succeed.

    b. Maintainability

       If the script is one which serves some important purpose, and
       will need to be maintained after you get promoted, it's more
       likely that a maintainer can be found for a shell script than
       for other scripting languages (especially less used ones such
       as ruby, rexx, etc).

    c. Policy

       Sometimes you're just told what to use :-)

======================================================================

19. Why shouldn't I use csh?

   http://www.softlab.ntua.gr/facilities/documentation/unix/grymoire/CshTop10.txt
   http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/
   
======================================================================

20. How do I reverse a file?

    Non-standard commands to do so are GNU tac and "tail -r". sed
    '1!G;h;$!d' is subject to sed limitation on the size of its hold
    space and is generally slow.
    
    The awk equivalent would be:
    
    awk '{l[n++]=$0}END{while(n--)print l[n]}'
    It stores the whole file in memory.
    
    The best approach in terms of efficiency portability and resource
    cosumption seems to be:
    
    cat -n | sort -rn | cut -f2-

    "cat -n" is not POSIX but appears to be fairly
    portable. Alternatives are "grep -n '^'", "awk '{print NR,$0}'",
    "nl" can't be used as it processes page headings.

======================================================================

21. how do I remove the last n lines?

    First we need to tell the code how many lines we want to cut
    from the bottom of a file.

      X=10

    Then We can do this:

      echo $(head -$(( $(wc -l < file ) - $X )) file) >\
      $$ && cat $$ > file && rm $$

       The break down:
       1) $(wc -l < file)
          Find out how many lines are in the file. Need to use
          redirection so wc won't print the file name.
       2) $(( $lines_in_file - $X ))
          Take the output from step one and do some math to find out
          how many lines we want to have when all is said and done.
       3) head -$lines_when_said_and_done file
          extract all but the unwanted lines from the file
       4) echo $all_but_the_unwanted_lines_from_the_file > $$
          this puts those lines into a temp file that has the name of
          the pid of the current shell.
       5) && cat $$ > file
          if everything has worked so far then cat the temp file into
          the original file. This is better than mv or cp because it
          insures that the permissions of the temp file do not
          override with the perms of the original file.
       6) && rm $$
          Remove the temp file.

    NAWK solution:
      $file=whatever
      nawk -v count="`nawk 'END{print NR}' $file`" 'NR<(count-11)' $file))

    AWK solution:
      awk 'BEGIN{n=12} NR>n{print a[NR%n]} {a[NR%n]=$0}' terrcode.d > new_terrcode.d
      or
      awk 'NR>n{print a[NR%n]} {a[NR%n]=$0}' n=12 terrcode.d > new_terrcode.d

    Command soltion:
      Using the last with the '-f' option to do somthing it wasn't
      intended for.

    $SHELL/sed/mv solution:
      for F in * (or however you get a list of files))) ; do
          L=`wc -l <$F`
          DL=`expr $L - 12`
          sed "$DL,\$d" $F >new$F
          mv new$F $F
      done

    $SHELL/head solution:
       i=`wc -l filename | cut -c1-8`
       head -n `expr $i - 12` filename
     
    PERL solution:
       #!/usr/local/bin/perl -w
       #Opposite of tail; print all but the last n lines of a file (or
       #stdin)
       #Also does skips first n lines if arg is
       #in the format "<#lines>.<#lines>"
       #Usage: notail [-][#headerlines.]<#taillines> [file(s)...]
       #Author: Douglas Wilson 5/18/1998
       #Use at your own risk
       #No warranties implied or expressed
       my $ahead=shift;
       $ahead=~s/^-//;
       my $dots=0;
       ++$dots while $ahead =~ /(.*?)\./g;
       exit 1 if ($dots>1);
       exit 1 if ($ahead !~ /[\d.]/);
       if ($dots==1) {
        (my $skip, $ahead)=split(/\./,$ahead);
        if ($skip) {
         my $i=0;
         while (<>) {
          last if (++$i >= $skip)
         }
        }
       }
       my $i=0;
       my @str_buf;
       if ($ahead) {
        while (<>) {
         $str_buf[$i]=$_;
         last if (++$i >= $ahead);
        }
       }
       if ($i+1 > $ahead) {
        $i=0;
        while (<>) {
         print $str_buf[$i];
         $str_buf[$i++]=$_;
         $i%=$ahead;
        }
       }

    Using GNU dd:

      ls -l file.txt | {
        IFS=" "
        read z z z z sz z
        last=`tail -10 file.txt | wc -c`
        dd bs=1 seek=`expr $sz - $last` if=/dev/null of=file.txt
      }

======================================================================

22. how do I get file size, or file modification time?

    ls will tell you all the things you want to know. From the man
    page for ls we learn about "ls -l" the file mode, the number of
    links to the file, the owner name, the group name, the size of the
    file (in bytes), the timestamp, and the filename. For the file
    size in human readable formate use the "-h" option.

    For example:

    $ ls -l timeTravel.html
    -rw-rw-r-- 1 user user 20624 Jun 19 2002 timeTravel1.html

    so to get the file size:

    $ set -- `ls -l timeTravel1.html`
    $ echo $5
    20624

    Note that ls doesn't always give the date in the same
    format. Check the man page for ls on your system if that
    matters. If you're interested in the file modification time, a
    better approach would be to use stat(1) if your system has
    it.

    Another possibility is to use GNU ls, which has a -T option giving
    complete time information for the file, including month, day,
    hour, minute, second and year.

======================================================================

23. How do I get a process id given a process name? Or, how do I find
    out if a process is still running, given a process ID?

    There isn't a reliable way to to this portably in the shell. Some
    systems reuse process ids much like file descriptors. That is,
    they use the lowest numbered pid which is not currently in use
    when starting a new process. That means that the pid you're
    looking for is there, but might not refer to the process you think
    it does.

    The usual approach is to parse the output of ps, but that involves
    a race condition, since the pid you find that way may not refer to
    the same process when you actually do something with that
    pid. There's no good way around that in a shell script though, so
    be advised that you might be stepping into a trap.

    One suggestion is to use pgrep if on Solaris, and 'ps h -o pid -C
    $STRING' if not, and your ps supports that syntax, but neither of
    those are perfect or ubiquitous.

    The normal solution when writing C programs is to create a pid
    file, and then lock it with fcntl(2). Then, if another program
    wants to know if that program is really running, it can attempt to
    gain a lock on the file. If the lock attempt fails, then it knows
    the file is still running.

    We don't have options in the shell like that, unless we can supply
    a C program which can try the lock for the script. Even so, the
    race condition described above still exists.

======================================================================

24. How do I get a script to update my current environment?

    Processes in unix cannot update the environment of the process
    that spawned them. Consequently you cannot run another process
    normally and expect it to do that, since it will be a child of the
    running process. There are a couple ways it can be done though.

    a. source the script

       This means that you use whatever syntax your shell has to read
       the desired script into the current environment.

       In Bourne derived shells (sh/ksh/bash/POSIX/etc) the syntax
       would be

         $ . script

       In csh type shells this would be

         $ source script

     b. use eval

        The eval command constructs a command by evaluating and then
        executing a set of arguments. If those arguments evaluate to a
        shell variable assignment, the current environment will be
        updated. For example

          --- exportFoo
          #!/bin/ksh
          echo export FOO=bar

        If you run this like

          eval `exportFoo`

        the value of FOO will be set to 'bar' in the calling shell.

        However, be aware that eval'ing a script written in another
        shell could turn out to be the wrong thing to do. For example,
        eval'ing this from a ksh script

        #!/bin/csh
        echo setenv FOO bar

        Would not do what you expect. It would produce an error,
        because ksh doesn't have a setenv command.

======================================================================

25. How do I rename *.foo to *.bar?

    In ksh/bash

      $ ls *.foo | while read f;do mv "$f" "${f%.*}".bar

    More generically (should work with all bourne derived shells)

      $ ls *.foo | while read f;do mv "$f" `basename "$f" .foo`.bar

    Or, in some linux distributions you may be able to use the rename
    command

      $ rename .foo .bar *

    If not (Debian, for one, comes with a perl version of rename that
    won't work with this) try

      $ rename 's/.foo/.bar/' *.foo

    More options, and much more discussion about this, is available
    from http://www.faqs.org/faqs/unix-faq/faq/part2/section-6.html

    Note that for file specifications which don't match existing
    files, the shell usually responds with something like "ls: *.foo:
    No such file or directory", which will mess up your processing of
    file names.

    Probably a safer alternative is to first check whether there are
    such files before trying to work with the output of ls. For
    example:

       #!/bin/sh
       ls *.foo >/dev/null 2>&1
       if [ $? -eq 0 ];then
         ls *.foo | while read file;do
           # do something with file
          done
       fi

     The pipe may not be necessary, but it prevents errors related to
     having so many filenames that they won't fit in the command line
     buffer.

     Note that this will not find dotfiles, you'd need a separate test
     for that.

======================================================================

26. How do I use shell variables in awk scripts

    Depending on the version of awk being used, either use the -v
    command line option,

      $ awk -v var=xxx '{print $0,var}'

    or add the variable after the command, as in

      $ awk '{print $0,var}' file var=xxx

    See the man page for awk on your system to see which is
    applicable.

======================================================================

27. How do I get input from the user with a timeout?

    In bash you can use the read built-in with the "-t" option.

======================================================================

28. How do I get one character input from the user?

    In bash this can be done with the "-n" option to read.

    More portably:

    OLDSTTY=$(stty -g) # save our terminal settings
    stty cbreak # enable independent processing of each input character
    ONECHAR=$(dd bs=1 count=1 2>/dev/null) # read one byte from standard in
    stty $OLDSTTY # restore the terminal settings

    Use the `something` format if your shell doesn't understand
    $(something). This reads from standard input, which may or may not
    be desirable. If you want to read from the terminal regardless of
    where standard input is, add "if=$(tty)" to the dd command.

======================================================================

29. why isn't my .profile read?

    ~/.profile is only read for login shells. In short if you don't
    see a login prompt then your ~/.profile isn't being read. You
    can fix this by either porting all of the things in your
    ~/.profile to /etc/profile or your shells rc script such as
    ~/.bashrc or ~/.zshrc.

    You may have to set the ENV variable in your login shell to get
    the .*rc shell read. See the man page for your shell to understand
    how it works.

======================================================================

30. why do I get "[5" not found in "[$1 -eq 2]"?

    Because you didn't RTFM :-)

    "[" is an alias for the "test" command. As such, it's called by a
    script like any other command (this applies even if test is
    builtin). Since the command line uses spaces to separate a command
    from its arguments, you have to put a space between '[' and its
    argument. So:

      $ [ -f xxx ] isn't the same as
      $ [-f xxx ]

    In the latter case, the shell will think that "[-f" is the
    command, not "[" with arguments "-f xxx ]

======================================================================

31. How do I exactly display the content of $var (with a \n appended).

    A: on POSIX systems or with shells with builtin printf (bash2,
    ksh93, zsh4.1, dash...)

    printf '%s\n' "$var"

    (except for memory/environment full errors, should be expected
    to work at least if $var is not longer than LINE_MAX (supposed
    to be at least _POSIX2_LINE_MAX == 2048), no harcoded limits in
    zsh/ksh/bash/dash builtins)

    ksh, zsh:
    print -r -- "$var"

    zsh:
    echo -E - "$var"

    Other bourne like shells:

    cat << EOF
    $var
    EOF
    (creates a temporary file and forks a process)

    expr "x$var" : 'x\(.*\)'
    (limited to 126 characters with some exprs, may return a
    non-null exit code).

    With ash:
    (unset a; ${a?$var}) 2>&1

======================================================================

32. How do I exactly display the content of $var (without a \n
    appended).

    printf %s "$var" # posix
    print -rn -- "$var" # zsh/ksh
    echo -nE - "$var" # zsh

    awk 'NR>1{print ""}{printf("%s",$0)}' << EOF
    $var
    EOF

    (with awk limitations [line length and number of fields])

======================================================================

Appendix A: Examples

  Web sites:
  -------------------------------------------

    Heiner Steven's Shelldorado site has quite a few example scripts,
    tutorials, and links to other such places.

      http://www.shelldorado.com/

  Arbitrary Date Arithmetic
  -------------------------------------------

    From: Tapani Tarvainen (tt@it.jyu.fi)
    Subject: Re: Date Calculation on shell programming
    View: Complete Thread (15 articles)
    Original Format
    Newsgroups: comp.unix.shell
    Date: 2002-04-24 22:30:04 PST

    patrick_cylo@hotmail.com (Patrick lo) writes:

> I would like to ask if it is possible to calculate the date on shell
> programming? For example, I would like to use the command "date" to
> output the date of seven days before today in the format "YYYY-MM-DD".
> Can I do that in a simple way?

    With standard shell commands it's not simple, but definitely doable.
    (If you can use perl or have Gnu date it's much easier.)

    The following functions should work with ksh, bash or any
    POSIX-compliant shell. By converting dates to and from Julian
    Day Numbers you can calculate differences between dates as well
    as those "date N days before/after" -type calculations.
    E.g., to get seven days back:

    julian2date $(( $(date2julian $DAY $MONTH $YEAR) - 7 ))

    Rearranging the arguments to use YYYY-MM-DD format is left as
    an exersize to the reader.

    --------------------------cut here---------------------------
    #! /usr/bin/sh

    # Date calculations using POSIX shell
    # Tapani Tarvainen July 1998, February 2001 (POSIXified)
    # This code is in the public domain.

    # Julian Day Number from calendar date
    date2julian() # day month year
    {
      day=$1; month=$2; year=$3
      tmpmonth=$((12 * year + month - 3))
      tmpyear=$((tmpmonth / 12))
      echo $(( (734 * tmpmonth + 15) / 24 - 2 * tmpyear + \
      tmpyear/4 - tmpyear/100 + tmpyear/400 + day + 1721119 ))
    }

    # Calendar date from Julian Day Number
    julian2date() # julianday
    {
      tmpday=$(($1 - 1721119))
      centuries=$(( (4 * tmpday - 1) / 146097))
      tmpday=$((tmpday + centuries - centuries/4))
      year=$(( (4 * tmpday - 1) / 1461))
      tmpday=$((tmpday - (1461 * year) / 4))
      month=$(( (10 * tmpday - 5) / 306))
      day=$((tmpday - (306 * month + 5) / 10))
      month=$((month + 2))
      year=$((year + month/12))
      month=$((month % 12 + 1))
      echo $day $month $year
    }

    # Day of week, Monday=1...Sunday=7
    dow() # day month year
    {
      echo $(( $(date2julian $1 $2 $3) % 7 + 1 ))
    }

    ########################## The End ##########################
    ---------------------------cut here--------------------------

    --
    Tapani Tarvainen

======================================================================

Appendix B: References


Loading