Showing posts with label shell. Show all posts
Showing posts with label shell. Show all posts

Tuesday, March 27, 2012

What Week Day Is That Date in the Past?

A solution for: http://www.linuxjournal.com/content/work-shell-what-day-date-past

No AWK involved.


#!/bin/bash


# We need 3 arguments:
# DOW, Month and Day


if [ $# -ne 3 ]
then
        echo "Usage: $(basename $0) weekday month day"
        echo "(example: $(basename $0) 4 3 2)"
        exit 1
fi


# We use the date command for checking


date -d "$2/$3" &> /dev/null


if [ "$?" == "1" ]
then
        echo "$(basename $0) Error: Invalid date!"
        exit 1
fi


# Month as number
M=$(date -d "$2/$3" +%m)


# The day of the month
D=$(date -d "$2/$3" +%e | sed 's/ //')


WD=$1


# The day of year is gonna help us decide which is
# the first year to test: current year or the previous one
DOY=$(date -d "$2/$3" +%j)
CDOY=$(date +%j)


Y=$(date +%Y)


if [ ${DOY} -gt ${CDOY} ]
then
        Y=$(expr ${Y} - 1)
fi


echo $M $D $WD $DOY $CDOY $Y


F=''
until [ -n "${F}" ]
do
        echo -n $Y $M =


        # The calendar for the month and year
        # Extract the line with the day of the month
        # Add numbers to the lines, the number will be
        # used to identify the Day of the Week
        # Extract the line of the particular day
        # gotta be careful cause 6 1 is the sixth line day 1
        # and 1 6 is the first line day 6, so we search by the second number
        # Convert the multiple spaces in front of the numbers into one space
        # so the cut command always return the second field
        # Extract the first number only, that's the DOW


        CWD=$(cal ${M} ${Y} \

                | grep "\b${D}\b" \
                | sed 's/[ ][ ][ ]\|[ ][ ]\|[ ]/|/g; s/^|//; s/|/\n/g' \
                | cat -n \
                | grep "\b${D}\b$" \
                | tr -s [:space:] ' ' \
                | cut -d ' ' -f 2)


        if [ ${WD} -eq ${CWD} ]
        then
                F=Found
        fi


        echo $CWD


        Y=$(expr ${Y} - 1)
done


##END##

The output of searching the first date in the past when March 1 was Wednesday:

janeiros@harlie:~/tmp$ ./t.sh 4 3 1
03 1 4 061 087 2012
2012 03 =5
2011 03 =3
2010 03 =2
2009 03 =1
2008 03 =7
2007 03 =5
2006 03 =4

janeiros@harlie:~/tmp$ cal 3 2006
     March 2006
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31



Thursday, September 23, 2010

Avoid that grep command in grep output

Hello everyone,

I'm planning to send everyday at least a message to the list with something interesting about Linux. This is the first one.

One week ago I bought the book "Official Ubuntu Server Book, The (2nd Edition)". Being a command line fan I went directly to the section "Cool tips and tricks". I started reading and I found out that the explanation of the first trick is in a certain way wrong. 

The idea behind the trick is to avoid the appearance of grep in resulting list for a command like:

janeiros@harlie:~$ ps ax | grep bash
13235 tty1     S      0:00 -bash
13256 tty1     S+     0:00 /bin/bash /usr/bin/startx
16911 pts/0    Ss+    0:00 -bash
17005 pts/1    Ss     0:00 -bash
17152 pts/1    S+     0:00 grep --color=auto bash

The authors suggest a "trick" running the command in this form:

janeiros@harlie:~$ ps ax | grep [b]ash
 How come when I try the trick is OK the first time and wrong at the second one?

janeiros@harlie:~$ ps ax | grep [b]ash
13235 tty1     S      0:00 -bash
13256 tty1     S+     0:00 /bin/bash /usr/bin/startx
16911 pts/0    Ss+    0:00 -bash
17005 pts/1    Ss     0:00 -bash

You can see grep is gone.

And now is back:

janeiros@harlie:~$ ps ax | grep [b]ash
13235 tty1     S      0:00 -bash
13256 tty1     S+     0:00 /bin/bash /usr/bin/startx
16911 pts/0    Ss+    0:00 -bash
17005 pts/1    Ss     0:00 -bash
17185 pts/1    S+     0:00 grep --color=auto bash

Of course, I did something in between that provoked the change! What was it? Let see if somebody find out what was. The authors' explanation for the trick is not exact!

The explanation is right there at the command line!

If nobody finds out I'll tell you later today.

By the way there is an alternative to the trick but it's longer.

The outputs of the commands weren't edited at all.

And by the way, something the author didn't tell: In Linux we can avoid the whole grep filtering with a simple ps -C

janeiros@harlie:~$ ps -C bash
  PID TTY          TIME CMD
13235 tty1     00:00:00 bash
17798 pts/0    00:00:00 bash

Of course, the output is a little bit different than the one from ps ax.

Hello,

The cause that is causing the trick to fail the second time is the existence in the current directory of a file with the name bash.

You can test it by going to a directory where you have write permissions, run the command the first time (ps -ef | grep [b]ash), then run the command touch bash and finally run ps -ef | grep [b]ash to see it fail.

The reason the command fails is due to something call "file expansion"at the shell level.

In the book, the authors say, and I quote:

"This works because of the power of regular expressions. When I surround the first character with brackets, I’m telling grep to search within a character class."

That's partially true 'cause they are forgetting that the shell is in the middle doing some "massage" to the argument, that massage is called "file name expansion." When the shell sees an argument with one of the special characters * or ? or [ it considers it as a pattern and replaces it with an alphabetical sorted list of filenames. If there is no filename that fits the pattern then the argument is left without change (depending of the shell option nullglob).

So, if you try to use the trick but in the directory exists a file that fits the pattern then the shell is gonna do the substitution and feed the command (grep) with the filename, bash in this case, instead of [b]ash. One solution to avoid the pattern scanning is using double quotes around the argument.

Below is a small script that let you test the whole thing:

#!/bin/bash

echo "$1"       # Don't forget to type the double quotes!

##END##

You can run the script with the [b]ash without the file bash in the directory and with the file in it and see the argument that the script is receiving from the shell.

Have a good night.
-- 
J. E. Aneiros
GNU/Linux User #190716 en http://counter.li.org
perl -e '$_=pack(c5,0105,0107,0123,0132,(1<<3)+2);y[A-Z][N-ZA-M];print;'
PK fingerprint: 5179 917E 5B34 F073 E11A  AFB3 4CB3 5301 4A80 F674

Servers, clients, logs and shell arithmetics

Hello everyone,

Let's say that we have a client that connects to a server through the network. The application's protocol specifies that the the client should maintain a session and send a message (heartbeat) every 30 seconds to keep the session alive. We have a log file with lines like the one below:

[2010-09-16 08:41:01.966] CLD -> HeartBeat-Sent

If we want to see if everything is OK we could take the last line of the file and examine the timestamp against the local time of the machine. I decide to take only the minutes and seconds and create a number like 1401 and compare that number against the local time in the same format. The line of the shell script that do that part is below:

if [ $(( ${TIME_HERE} - ${TIME_THERE} )) -gt ${INTERVAL} ]
then
    STATUS="ERROR"
else
    STATUS="OK"
fi

There is a potential runtime error in that block of code. Any idea what could be wrong? Assume both machines are time synchronized to the milliseconds resolution.

The problem in that sentence is related to the way the shell interprets integers. When just one of  the operands in the expression (( ${TIME_HERE} - ${TIME_THERE} )) is something like 0008, the shell cries out.

The reason for that cry is that the shell thinks the number is an octal number so the 8 is not a valid digit in that base (0..7). You can see it with a command like:

janeiros@harlie:~$ echo "$(( 1000 - 0008 ))"
-bash: 1000 - 0008: value too great for base (error token is "0008")

You can also verify that the shell is doing octal arithmetic with something like this:

janeiros@harlie:~$ echo "$(( 1000 - 0011 ))"
991

All this could be solve by using the expr command:

janeiros@harlie:~$ echo "$(expr 1000 - 0011)"
989

Octal numbers bring good memories to me, from the time I was in college and I got in touch with the first computer in my life, it was around the beginning of the Eighties. That computer was so primitive that it doesn't have a boot loader, well it doesn't have a disk either! So you have to load a small set of instruction to indicate the paper tape reader to read the monitor program 'cause the machine doesn't even have an operating system! The set of instructions to do that was in the form of octal number that you have to load in the machine as a binary digit representation of the octal number. For doing that you have a series of switches that you can switch to ON/OFF (0,1).

Later I got in touch with UNIX (1991), Interactive UNIX to be more precise (http://en.wikipedia.org/wiki/INTERACTIVE_UNIX). In UNIX I discovered that my useless knowledge of the octal representation was in fact very useful at the time of working with absolute file permission representation in chmod!

Have a good day!

--
J. E. Aneiros
GNU/Linux User #190716 en http://counter.li.org
perl -e '$_=pack(c5,0105,0107,0123,0132,(1<<3)+2);y[A-Z][N-ZA-M];print;'
PK fingerprint: 5179 917E 5B34 F073 E11A  AFB3 4CB3 5301 4A80 F674

Tuesday, April 13, 2010

Number of days in current Month in UNIX shell

cal | grep -v '[A-Za-z]' | wc -w

Last Sunday of the Month in UNIX shell

cal | grep '^[23]' | tail -1 | cut -d' ' -f1

Several months later I don't remember why the grep part?

cal | tail -1 | cut -d' ' -f1


AWK version:



#!/bin/bash


cal | awk '
    { last = $1 }
END { print last }'


##END##