Hai’s Blog

May 28, 2009

Use lassign to Assign Command-Line Parameters to Variables

Filed under: Programming, Tcl — Hai @ 5:52 pm

The Problem

You need to assign command-line parameters to variables. For example:

    set [lindex $argv 0] server
    set [lindex $argv 1] server
    set [lindex $argv 2] user
    set [lindex $argv 3] password

The problem with this approach is it takes lots of typing. Cut and paste can help reducing some of it, but it is error prone for the user might forget to edit the index numbers after pasting. When the number of command-line parameters increase, so is the chance for making error.

The Solution

One way to deal with assigning command-line parameters to variables is to use the lassign command from the Tclx package:

    package require Tclx
    lassign $argv server port user password

Not only this approach is cleaner and less error prone, it is also easier to understand. The user must make sure argv has enough elements to assign to the variables. If there is more variables than the number of command-line parameters, then the extra variables will be assigned the empty value {}:

    % lassign {1 2} a b c
    % puts "a=$a, b=$b, c=$c"
    a=1, b=2, c=

If there are more command-line parameters than variables, then lassign will return a list of unassigned parameters:

    % lassign {1 2 3 4 5} a b c
    4 5

Conclusion

The lassign command eases the task of assigning command-line parameters to variables. The user must exercise care to ensure the number of parameters matches the number of variables. Lassign can also be used for any list-variable assignment task the user can think of. Please take a look at the document for more usage and behavior notes.

May 14, 2009

Tcl: Use commandloop to Provide Interactive Access to Your Procedures

Filed under: Programming, Tcl — Hai @ 3:16 am

When I wrote text base programs in high-level programming languages, I often need to create a simple menu, get the user’s choice, then dispatch the appropriate procedure. The dispatch code often include lengthy swich (case) statement:

    switch (choice) {
	case 'a':
		function_a();
		break;
	case 'b':
		function_b();
		break;
	}
    ...

This design pattern was fine if the menu is relatively short. As the menu grows, adding a choice to the menu means: 1) Add lines to the menu, 2) update the switch statement, and 3) write another function to handle the new choice.

This design pattern bears several problems:

  1. Adding an additional choice to the menu requires a lot of works as illustrated
  2. Programmer must take care to keep the menu and the switch statement in sync.
  3. As the menu grows, the switch statement can get very long

Fortunately, Tcl offers an easy remedy to this problem: commandloop. I wrote about commandloop before as my way to setup a break point for debugging purpose. Commandloop can do more than that. In this case, I can commandloop to provide dispatch to my procedures. Consider the following simple script:

	package require Tclx

	set stack {}

	proc stack {} { set ::stack }
	proc push {item} { lappend ::stack $item }
	proc pop  {}     {
		set item [lindex $::stack end]
		set ::stack [lreplace $::stack end end]
		return $item
	}

	commandloop

This script simulates a stack; it provides three commands (procedures): stack to display the contents of the stack and the self-explanatory push and pop. The script’s main body consists of a single commandloop command. So how does this works?

The commandloop command allow the users to interact with the script, including the ability to call the script’s commands. Let’s take a look at an interactive session:

    $ ./commandloop1.tcl
    %stack
    %push 5
    5
    %push 7
    5 7
    %push -3
    5 7 -3
    %stack
    5 7 -3
    %pop
    -3
    %stack
    5 7
    %set x 0
    %push $x
    5 7 0
    %puts "Stack contents: [stack]"
    Stack contents: 5 7 0
    %exit
    $

After launching the script, we arrive at the ‘%’ prompt. From there, we can issue the script’s commands such as stack, push, and pop. Not only that, we can also set additional variables (such as x). We can all issue other commands such as puts. In short, the commandloop command opens up an interpreter to interact with the script. Finally, the exit command (or Ctrl-D in Linux, Ctrl-Z in Windows) exits the command loop and ends the script.

Commandloop comes with some handy optional flags. The first is the -endcommand flag. This flag specifies a command (or procedure) to execute after commandloop terminates. Note that after commandloop terminates, the script’s execution will stop, ignoring any command that follows. If you want to execute a command after the commandloop terminates, use this flag.

Another flag of interest is the -prompt1 flag. This flag allows the users to customize the prompt if they don’t like the default one. These are the two flags I use most often. For information regarding other flags, you can look up information for commandloop in the Tclx package.

May 12, 2009

Debugging Tcl Script with commandloop

Filed under: Programming, Tcl — Hai @ 6:32 am

Besides using puts to debug my Tcl script, I also like this technique, which set up a break point within my script. There are times when I cannot use that technique, I use the commandloop command to create an impromptu break point.

Before we get started, let me clarify that the purpose of commandloop is not for debugging, but that is what I am using it for. Consider the following block of code:

    # Given a number x, double it
    proc double {x} {
        upvar 1 x xValue
        set xValue [expr {$xValue * 2}]
    }

Those who knows Tcl can spot the bug right away, but for argument sake, let’s pretend that the bug was well hidden and I need to set a break point to debug the procedure double. I can accomplish this goal using the commandloop command:

    package require Tclx

    # Given a number x, double it
    proc double {x} {
        upvar 1 x xValue
        commandloop
        set xValue [expr {$xValue * 2}]
    }

During execution, commandloop will halt the code and display the ‘%’ prompt. At this point, I can execute any Tcl command to debug the problem:

    $ tclsh db.tcl
    %set x
    n
    %set xValue
    Error: can't read "xValue": no such variable
    %exit

    $

In the capture above, the script stopped just after the upvar line and displayed the ‘%’ prompt. I then issue some Tcl commands to debug and finally was able to pinpoint the problem–the upvar line should have been:

    upvar 1 $x xValue

One thing to note, unlike the aforementioned solution, commandloop has one disadvantage: upon typing exit, the script will exit right away, ignoring the rest of the script. However, in a crunch, this command provides a quick way to set break point to debug my script.

Use for_file to Ease Line-by-Line Processing

Filed under: Programming, Tcl — Hai @ 5:52 am

In my job, I often need to open a file, read and process each line until the end. In Tcl, that pattern can be translated as:

	set infile [open file.txt r]
	while {[gets $infile line] >= 0} {
		# do something with $line...
	}
	close $infile

Simple? Yes, but I can still see room for improvements. The Tclx package has a for_file command which can simplify the coding quite a bit:

	package require Tclx
	for_file line file.txt {
		# do something with $line...
	}

Not only I don’t have to worry about openning and closing the file, I don’t have to deal with the lengthy while/gets command, which can be error-prone. Finally, the second construct is much cleaner and easier to understand.

After learning about the for_file command, I discovered that the fileutil package also has a similar command:

	package require fileutil
	fileutil::foreachLine line file.txt {
		# do something with $line...
	}

It seems either one of them will get the job done. Personally, I prefer the for_file command because it is shorter. If you are aware of any differences between the two, please comment.

May 8, 2009

Display Weather from Command Line

Filed under: Tcl, Unix — Hai @ 10:19 pm

Since I work on Linux/OSX command line all day, I prefer to get my information such as stock quote or weather via command line. Here is a script to do that.

A couple of notes:

  1. I use curl instead of the TclCurl package because my system does not come with TclCurl
  2. The script employs Google Weather API to retrieve weather data. The rest of the script deals with converting that data from XML format to human-readable format.
  3. I use this script as part of my geek tool output.

Here is a sample output of the script:

$ weather
Seattle, WA 98121 2009-05-08 22:15:09 +0000
  Condition      : Overcast
  Temperature (F): 61
  Humidity       : 47%
  Wind           : NE at 2 mph

  Fri 63 45 Mostly Sunny
  Sat 68 47 Mostly Sunny
  Sun 67 49 Mostly Sunny
  Mon 61 47 Chance of Showers 

Bothell, WA 98012 2009-05-08 22:18:51 +0000
  Condition      : Cloudy
  Temperature (F): 55
  Humidity       : 58%
  Wind           : N at 2 mph

  Fri 59 41 Mostly Sunny
  Sat 63 43 Mostly Sunny
  Sun 65 45 Mostly Sunny
  Mon 58 41 Chance of Showers 

Here is the script itself:

#!/usr/bin/env tclsh

package require tdom

# Provide the default zip codes of none specified
if {$argc == 0} { set argv {98121 98012} }

foreach location $argv {
    set xml [exec curl --silent http://www.google.com/ig/api?weather=$location]

    set doc [dom parse $xml]
    set root [$doc documentElement]

    #
    # Show Forecast Information
    #

    set inf [$doc getElementsByTagName forecast_information]
    set data ""
    append data "[[$inf selectNodes city] getAttribute data] "
    append data "[[$inf selectNodes postal_code] getAttribute data] "
    append data "[[$inf selectNodes current_date_time] getAttribute data] "
    puts "$data"

    #
    # Show Current Condition
    #
    set currentConditions [$doc getElementsByTagName current_conditions]

    set labels {
        condition      "Condition"
        temp_f         "Temperature (F)"
        humidity       "Humidity"
        wind_condition "Wind"
    }

    set longest 0
    foreach {tag labl} $labels {
        set length [string length $labl]
        if {$length > $longest} { set longest $length }
    }

    foreach {tag labl} $labels {
        set node [$currentConditions selectNodes $tag]
        set data [$node getAttribute data]
        regsub {[^:]*: *} $data "" data
        puts [format "  %-*s: %s" $longest $labl $data]
    }

    puts ""

    # Show Conditions for Next Days
    foreach cond [$doc getElementsByTagName forecast_conditions] {
        set data ""
        append data "[[$cond selectNodes day_of_week] getAttribute data] "
        append data "[[$cond selectNodes high] getAttribute data] "
        append data "[[$cond selectNodes low] getAttribute data] "
        append data "[[$cond selectNodes condition] getAttribute data] "
        puts "  $data"
    }
    puts ""
}

May 6, 2009

View Stock Price Using Command Line

Filed under: Finance, Tips & Tricks, Unix — Hai @ 4:21 pm

I live on command line all day, so it is convenience to perform many tasks using the command line. One of those is to check stock price:

curl -s 'http://download.finance.yahoo.com/d/quotes.csv?s=csco&f=l1'

The above command check for the last price (with some delay) of Cisco Systems. If you want to check the price for other tickers, replace ‘csco’ with your ticker symbol. You can query more than one symbols by separate them with commas, but do not leave any space in between.

The f=l1 defines the formatting for the returned information. To find more about formatting, visit this page

Please note this trick requires curl so install it before you try.

May 4, 2009

Locate a File and Go to It

Filed under: Unix — Hai @ 3:58 am

I often need to find a file, then cd to its directory. This command will do both, provide that the file’s name is unique enough:

$ cd $(dirname $(find ~ -name emails.txt))

Please note that the above command works for bash shell, but not csh: csh’s equivalent to the $( … ) construct is the ` … ` construct which the bash shell also understands. However, the “ construct does not allow for nesting.

April 30, 2009

One-Liner to Display My Computer’s IP Address

Filed under: Unix — Hai @ 6:02 am

Here is yet another way to diplay a list of IP address associated with the current computer.

python -c "import socket; print '\n'.join(socket.gethostbyname_ex(socket.gethostname())[2])"

The socket.gethostname() call returns the host name of the computer. The socket.gethostbyname_ex() call returns a list of three items: The host name, the list of aliases for this host, and a list of IP addresses. Recall that Python’s array starts with index 0, the socket.gethostbyname_ex(…)[2] expression refers to the list of IP addresses. Finally, the print statement prints out the IP addresses, one per line.

The No-Tulip Festival

Filed under: Photography — Hai @ 4:58 am

This year we once again went to the Skagit County Tulip Festival. However, we were in a bit of surprise: the fields are yet to bloom due to the colder-than-usual winter.

Instead of driving around and watch the disappointing fields, we decided to visit the Roozengaarde and enjoyed their spectacular tulip patches. I took most of my photos in this garden.

Although the festival was about tulips, I managed to take a couple of shots of the dafodils, which are underrated in my opinion.

Here is a short video I took that day, just to test drive my Kodak Zi6 pocket HD camcorder.

For more pictures I took that day, visit my gallery.

April 25, 2009

Changing Directory Listing (ls) Color in Linux and Mac OS X

Filed under: Unix — Hai @ 6:53 am

Problem

I practically “live” on the Linux command line and one thing that has been bugging me for so long that now I decided to do something about it. On my company’s Linux systems the ls command produces color output, which makes it easy to distinguish different file types. However, the directories are dark blue which makes it nearly impossible to see against the black background.

My Solution

To fix the problem, my research took me to the LS_COLORS environment variable and a command named dircolors. The solution is to set the LS_COLORS environment variable to control how the ls command chooses its color. I can set the LS_COLORS variable manually, but there is a better way which employes the dircolors command.

The dircolors command output commands to set the LS_COLORS environment. It also output the colors in human-readable format, allowing easy modifications.

To use the dircolors, the first step is to save the current settings into a file. From the terminal, I issued one of the following commands:

    dircolors -p > ~/dircolors.txt

The next step is to edit the file ~/dircolors.txt. This file’s format is easy to understand and self-documented; I had no problem finding the file that begins with “DIR” and change the color to my taste.

Next, I try out the new color scheme:

    eval $( dircolors -b ~/dircolors.txt ); ls # bash shell syntax
    eval `dircolors -b ~/dircolors.txt`; ls # C shell syntax

After finding the color scheme I liked, I saved it to my start up file:

    dircolors -b ~/dircolors.txt >> ~/.profile # bash shell
    dircolors -b ~/dircolors.txt >> ~/.cshrc   # C shell

From this point on, I no longer have to put up with hard-to-see colors.

Solution for BSD Systems

On BSD systems, which includes the Mac OS X, the variable in question is LSCOLOR (note the lack of the underscore character). The format of this variable is different from the Linux’s LS_COLORS. The default is exfxcxdxbxegedabagacad. The value of this variable consists of pairs of characters; the first character is the code for foreground color, and the second is for background. Please consult the man page for ls on BSD for more information.

The Interactive Solution

While I like to do things the hard way to learn more about the inner-working of the OS, there is a more interactive way: point your browser here to an interactive online web application which assist you in setting the colors. After finding your desired color scheme, you still have to cut and paste it to your start up file.

Additional References

Google is your friend: do a search for LSCOLORS or LS_COLORS will result in more information that you ever care to read.

Older Posts »

Blog at WordPress.com.