Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
Prev | Chapter 36. Miscellany | Next

# 36.7. Assorted Tips

## 36.7.1. Ideas for more powerful scripts

- You have a problem that you want to solve by writing a Bash script. Unfortunately, you don't know quite where to start. One method is to plunge right in and code those parts of the script that come easily, and write the hard parts as pseudo-code.

         1 #!/bin/bash
         2
         3 ARGCOUNT=1                     # Need name as argument.
         4 E_WRONGARGS=65
         5
         6 if [ number-of-arguments is-not-equal-to "$ARGCOUNT" ]
         7 #    ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
         8 #  Can't figure out how to code this . . .
         9 #+ . . . so write it in pseudo-code.
        10
        11 then
        12   echo "Usage: name-of-script name"
        13   #            ^^^^^^^^^^^^^^     More pseudo-code.
        14   exit $E_WRONGARGS
        15 fi
        16
        17 . . .
        18
        19 exit 0
        20
        21
        22 # Later on, substitute working code for the pseudo-code.
        23
        24 # Line 6 becomes:
        25 if [ $# -ne "$ARGCOUNT" ]
        26
        27 # Line 12 becomes:
        28   echo "Usage: `basename $0` name"

  For an example of using pseudo-code, see the Square Root exercise.

- To keep a record of which user scripts have run during a particular session or over a number of sessions, add the following lines to each script you want to keep track of. This will keep a continuing file record of the script names and invocation times.

         1 # Append (>>) following to end of each script tracked.
         2
         3 whoami>> $SAVE_FILE    # User invoking the script.
         4 echo $0>> $SAVE_FILE   # Script name.
         5 date>> $SAVE_FILE      # Date and time.
         6 echo>> $SAVE_FILE      # Blank line as separator.
         7
         8 #  Of course, SAVE_FILE defined and exported as environmental variable in ~/.bashrc
         9 #+ (something like ~/.scripts-run)

- The >> operator appends lines to a file. What if you wish to prepend a line to an existing file, that is, to paste it in at the beginning?

         1 file=data.txt
         2 title="***This is the title line of data text file***"
         3
         4 echo $title | cat - $file >$file.new
         5 # "cat -" concatenates stdout to $file.
         6 #  End result is
         7 #+ to write a new file with $title appended at *beginning*.

  This is a simplified variant of the Example 19-13 script given earlier. And, of course, sed can also do this.

- A shell script may act as an embedded command inside another shell script, a Tcl or wish script, or even a Makefile. It can be invoked as an external shell command in a C program using the `system()` call, i.e., `system("script_name");`.

- Setting a variable to the contents of an embedded sed or awk script increases the readability of the surrounding shell wrapper. See Example A-1 and Example 15-20.

- Put together files containing your favorite and most useful definitions and functions. As necessary, "include" one or more of these "library files" in scripts with either the dot (.) or source command.

         1 # SCRIPT LIBRARY
         2 # ------ -------
         3
         4 # Note:
         5 # No "#!" here.
         6 # No "live code" either.
         7
         8
         9 # Useful variable definitions
        10
        11 ROOT_UID=0             # Root has $UID 0.
        12 E_NOTROOT=101          # Not root user error.
        13 MAXRETVAL=255          # Maximum (positive) return value of a function.
        14 SUCCESS=0
        15 FAILURE=-1
        16
        17
        18
        19 # Functions
        20
        21 Usage ()               # "Usage:" message.
        22 {
        23   if [ -z "$1" ]       # No arg passed.
        24   then
        25     msg=filename
        26   else
        27     msg=$@
        28   fi
        29
        30   echo "Usage: `basename $0` "$msg""
        31 }
        32
        33
        34 Check_if_root ()       # Check if root running script.
        35 {                      # From "ex39.sh" example.
        36   if [ "$UID" -ne "$ROOT_UID" ]
        37   then
        38     echo "Must be root to run this script."
        39     exit $E_NOTROOT
        40   fi
        41 }
        42
        43
        44 CreateTempfileName ()  # Creates a "unique" temp filename.
        45 {                      # From "ex51.sh" example.
        46   prefix=temp
        47   suffix=`eval date +%s`
        48   Tempfilename=$prefix.$suffix
        49 }
        50
        51
        52 isalpha2 ()            # Tests whether *entire string* is alphabetic.
        53 {                      # From "isalpha.sh" example.
        54   [ $# -eq 1 ] || return $FAILURE
        55
        56   case $1 in
        57   *[!a-zA-Z]*|"") return $FAILURE;;
        58   *) return $SUCCESS;;
        59   esac                 # Thanks, S.C.
        60 }
        61
        62
        63 abs ()                           # Absolute value.
        64 {                                # Caution: Max return value = 255.
        65   E_ARGERR=-999999
        66
        67   if [ -z "$1" ]                 # Need arg passed.
        68   then
        69     return $E_ARGERR             # Obvious error value returned.
        70   fi
        71
        72   if [ "$1" -ge 0 ]              # If non-negative,
        73   then                           #
        74     absval=$1                    # stays as-is.
        75   else                           # Otherwise,
        76     let "absval = (( 0 - $1 ))"  # change sign.
        77   fi
        78
        79   return $absval
        80 }
        81
        82
        83 tolower ()             #  Converts string(s) passed as argument(s)
        84 {                      #+ to lowercase.
        85
        86   if [ -z "$1" ]       #  If no argument(s) passed,
        87   then                 #+ send error message
        88     echo "(null)"      #+ (C-style void-pointer error message)
        89     return             #+ and return from function.
        90   fi
        91
        92   echo "$@" | tr A-Z a-z
        93   # Translate all passed arguments ($@).
        94
        95   return
        96
        97 # Use command substitution to set a variable to function output.
        98 # For example:
        99 #    oldvar="A seT of miXed-caSe LEtTerS"
       100 #    newvar=`tolower "$oldvar"`
       101 #    echo "$newvar"    # a set of mixed-case letters
       102 #
       103 # Exercise: Rewrite this function to change lowercase passed argument(s)
       104 #           to uppercase ... toupper()  [easy].
       105 }

- Use special-purpose comment headers to increase clarity and legibility in scripts.

         1 ## Caution.
         2 rm -rf *.zzy   ##  The "-rf" options to "rm" are very dangerous,
         3                ##+ especially with wild cards.
         4
         5 #+ Line continuation.
         6 #  This is line 1
         7 #+ of a multi-line comment,
         8 #+ and this is the final line.
         9
        10 #* Note.
        11
        12 #o List item.
        13
        14 #> Another point of view.
        15 while [ "$var1" != "end" ]    #> while test "$var1" != "end"

- Dotan Barak contributes template code for a progress bar in a script.

  Example 36-17. A Progress Bar

         1 #!/bin/bash
         2 # progress-bar.sh
         3
         4 # Author: Dotan Barak (very minor revisions by ABS Guide author).
         5 # Used in ABS Guide with permission (thanks!).
         6
         7
         8 BAR_WIDTH=50
         9 BAR_CHAR_START="["
        10 BAR_CHAR_END="]"
        11 BAR_CHAR_EMPTY="."
        12 BAR_CHAR_FULL="="
        13 BRACKET_CHARS=2
        14 LIMIT=100
        15
        16 print_progress_bar()
        17 {
        18         # Calculate how many characters will be full.
        19         let "full_limit = ((($1 - $BRACKET_CHARS) * $2) / $LIMIT)"
        20
        21         # Calculate how many characters will be empty.
        22         let "empty_limit = ($1 - $BRACKET_CHARS) - ${full_limit}"
        23
        24         # Prepare the bar.
        25         bar_line="${BAR_CHAR_START}"
        26         for ((j=0; j<full_limit; j++)); do
        27                 bar_line="${bar_line}${BAR_CHAR_FULL}"
        28         done
        29
        30         for ((j=0; j<empty_limit; j++)); do
        31                 bar_line="${bar_line}${BAR_CHAR_EMPTY}"
        32         done
        33
        34         bar_line="${bar_line}${BAR_CHAR_END}"
        35
        36         printf "%3d%% %s" $2 ${bar_line}
        37 }
        38
        39 # Here is a sample of code that uses it.
        40 MAX_PERCENT=100
        41 for ((i=0; i<=MAX_PERCENT; i++)); do
        42         #
        43         usleep 10000
        44         # ... Or run some other commands ...
        45         #
        46         print_progress_bar ${BAR_WIDTH} ${i}
        47         echo -en "\r"
        48 done
        49
        50 echo ""
        51
        52 exit

- A particularly clever use of if-test constructs is for comment blocks.

         1 #!/bin/bash
         2
         3 COMMENT_BLOCK=
         4 #  Try setting the above variable to some value
         5 #+ for an unpleasant surprise.
         6
         7 if [ $COMMENT_BLOCK ]; then
         8
         9 Comment block --
        10 =================================
        11 This is a comment line.
        12 This is another comment line.
        13 This is yet another comment line.
        14 =================================
        15
        16 echo "This will not echo."
        17
        18 Comment blocks are error-free! Whee!
        19
        20 fi
        21
        22 echo "No more comments, please."
        23
        24 exit 0

  Compare this with using here documents to comment out code blocks.

- Using the $? exit status variable, a script may test if a parameter contains only digits, so it can be treated as an integer.

         1 #!/bin/bash
         2
         3 SUCCESS=0
         4 E_BADINPUT=85
         5
         6 test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
         7 # An integer is either equal to 0 or not equal to 0.
         8 # 2>/dev/null suppresses error message.
         9
        10 if [ $? -ne "$SUCCESS" ]
        11 then
        12   echo "Usage: `basename $0` integer-input"
        13   exit $E_BADINPUT
        14 fi
        15
        16 let "sum = $1 + 25"             # Would give error if $1 not integer.
        17 echo "Sum = $sum"
        18
        19 # Any variable, not just a command-line parameter, can be tested this way.
        20
        21 exit 0

- The 0 - 255 range for function return values is a severe limitation. Global variables and other workarounds are often problematic. An alternative method for a function to communicate a value back to the main body of the script is to have the function write to `stdout` (usually with echo) the "return value," and assign this to a variable. This is actually a variant of command substitution.

  Example 36-18. Return value trickery

         1 #!/bin/bash
         2 # multiplication.sh
         3
         4 multiply ()                     # Multiplies params passed.
         5 {                               # Will accept a variable number of args.
         6
         7   local product=1
         8
         9   until [ -z "$1" ]             # Until uses up arguments passed...
        10   do
        11     let "product *= $1"
        12     shift
        13   done
        14
        15   echo $product                 #  Will not echo to stdout,
        16 }                               #+ since this will be assigned to a variable.
        17
        18 mult1=15383; mult2=25211
        19 val1=`multiply $mult1 $mult2`
        20 # Assigns stdout (echo) of function to the variable val1.
        21 echo "$mult1 X $mult2 = $val1"                   # 387820813
        22
        23 mult1=25; mult2=5; mult3=20
        24 val2=`multiply $mult1 $mult2 $mult3`
        25 echo "$mult1 X $mult2 X $mult3 = $val2"          # 2500
        26
        27 mult1=188; mult2=37; mult3=25; mult4=47
        28 val3=`multiply $mult1 $mult2 $mult3 $mult4`
        29 echo "$mult1 X $mult2 X $mult3 X $mult4 = $val3" # 8173300
        30
        31 exit 0

  The same technique also works for alphanumeric strings. This means that a function can "return" a non-numeric value.

         1 capitalize_ichar ()          #  Capitalizes initial character
         2 {                            #+ of argument string(s) passed.
         3
         4   string0="$@"               # Accepts multiple arguments.
         5
         6   firstchar=${string0:0:1}   # First character.
         7   string1=${string0:1}       # Rest of string(s).
         8
         9   FirstChar=`echo "$firstchar" | tr a-z A-Z`
        10                              # Capitalize first character.
        11
        12   echo "$FirstChar$string1"  # Output to stdout.
        13
        14 }
        15
        16 newstring=`capitalize_ichar "every sentence should start with a capital letter."`
        17 echo "$newstring"          # Every sentence should start with a capital letter.

  It is even possible for a function to "return" multiple values with this method.

  Example 36-19. Even more return value trickery

         1 #!/bin/bash
         2 # sum-product.sh
         3 # A function may "return" more than one value.
         4
         5 sum_and_product ()   # Calculates both sum and product of passed args.
         6 {
         7   echo $(( $1 + $2 )) $(( $1 * $2 ))
         8 # Echoes to stdout each calculated value, separated by space.
         9 }
        10
        11 echo
        12 echo "Enter first number "
        13 read first
        14
        15 echo
        16 echo "Enter second number "
        17 read second
        18 echo
        19
        20 retval=`sum_and_product $first $second`      # Assigns output of function.
        21 sum=`echo "$retval" | awk '{print $1}'`      # Assigns first field.
        22 product=`echo "$retval" | awk '{print $2}'`  # Assigns second field.
        23
        24 echo "$first + $second = $sum"
        25 echo "$first * $second = $product"
        26 echo
        27
        28 exit 0

  There can be only one echo statement in the function for this to work. If you alter the previous example:

         1 sum_and_product ()
         2 {
         3   echo "This is the sum_and_product function." # This messes things up!
         4   echo $(( $1 + $2 )) $(( $1 * $2 ))
         5 }
         6 ...
         7 retval=`sum_and_product $first $second`      # Assigns output of function.
         8 # Now, this will not work correctly.

- Next in our bag of tricks are techniques for passing an array to a function, then "returning" an array back to the main body of the script.

  Passing an array involves loading the space-separated elements of the array into a variable with command substitution. Getting an array back as the "return value" from a function uses the previously mentioned strategem of echoing the array in the function, then invoking command substitution and the ( ... ) operator to assign it to an array.

  Example 36-20. Passing and returning arrays

         1 #!/bin/bash
         2 # array-function.sh: Passing an array to a function and ...
         3 #                   "returning" an array from a function
         4
         5
         6 Pass_Array ()
         7 {
         8   local passed_array   # Local variable!
         9   passed_array=( `echo "$1"` )
        10   echo "${passed_array[@]}"
        11   #  List all the elements of the new array
        12   #+ declared and set within the function.
        13 }
        14
        15
        16 original_array=( element1 element2 element3 element4 element5 )
        17
        18 echo
        19 echo "original_array = ${original_array[@]}"
        20 #                      List all elements of original array.
        21
        22
        23 # This is the trick that permits passing an array to a function.
        24 # **********************************
        25 argument=`echo ${original_array[@]}`
        26 # **********************************
        27 #  Pack a variable
        28 #+ with all the space-separated elements of the original array.
        29 #
        30 # Attempting to just pass the array itself will not work.
        31
        32
        33 # This is the trick that allows grabbing an array as a "return value".
        34 # *****************************************
        35 returned_array=( `Pass_Array "$argument"` )
        36 # *****************************************
        37 # Assign 'echoed' output of function to array variable.
        38
        39 echo "returned_array = ${returned_array[@]}"
        40
        41 echo "============================================================="
        42
        43 #  Now, try it again,
        44 #+ attempting to access (list) the array from outside the function.
        45 Pass_Array "$argument"
        46
        47 # The function itself lists the array, but ...
        48 #+ accessing the array from outside the function is forbidden.
        49 echo "Passed array (within function) = ${passed_array[@]}"
        50 # NULL VALUE since the array is a variable local to the function.
        51
        52 echo
        53
        54 ############################################
        55
        56 # And here is an even more explicit example:
        57
        58 ret_array ()
        59 {
        60   for element in {11..20}
        61   do
        62     echo "$element "   #  Echo individual elements
        63   done                 #+ of what will be assembled into an array.
        64 }
        65
        66 arr=( $(ret_array) )   #  Assemble into array.
        67
        68 echo "Capturing array \"arr\" from function ret_array () ..."
        69 echo "Third element of array \"arr\" is ${arr[2]}."   # 13  (zero-indexed)
        70 echo -n "Entire array is: "
        71 echo ${arr[@]}                # 11 12 13 14 15 16 17 18 19 20
        72
        73 echo
        74
        75 exit 0
        76
        77 #  Nathan Coulter points out that passing arrays with elements containing
        78 #+ whitespace breaks this example.

  For a more elaborate example of passing arrays to functions, see Example A-10.

- Using the double-parentheses construct, it is possible to use C-style syntax for setting and incrementing/decrementing variables and in for and while loops. See Example 11-13 and Example 11-18.

- Setting the path and umask at the beginning of a script makes it more portable -- more likely to run on a "foreign" machine whose user may have bollixed up the `$PATH` and umask.

         1 #!/bin/bash
         2 PATH=/bin:/usr/bin:/usr/local/bin ; export PATH
         3 umask 022   # Files that the script creates will have 755 permission.
         4
         5 # Thanks to Ian D. Allen, for this tip.

- A useful scripting technique is to repeatedly feed the output of a filter (by piping) back to the same filter, but with a different set of arguments and/or options. Especially suitable for this are tr and grep.

         1 # From "wstrings.sh" example.
         2
         3 wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
         4 tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

  Example 36-21. Fun with anagrams

         1 #!/bin/bash
         2 # agram.sh: Playing games with anagrams.
         3
         4 # Find anagrams of...
         5 LETTERSET=etaoinshrdlu
         6 FILTER='.......'       # How many letters minimum?
         7 #       1234567
         8
         9 anagram "$LETTERSET" | # Find all anagrams of the letterset...
        10 grep "$FILTER" |       # With at least 7 letters,
        11 grep '^is' |           # starting with 'is'
        12 grep -v 's$' |         # no plurals
        13 grep -v 'ed$'          # no past tense verbs
        14 # Possible to add many combinations of conditions and filters.
        15
        16 #  Uses "anagram" utility
        17 #+ that is part of the author's "yawl" word list package.
        18 #  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
        19 #  http://bash.deta.in/yawl-0.3.2.tar.gz
        20
        21 exit 0                 # End of code.
        22
        23
        24 bash$ sh agram.sh
        25 islander
        26 isolate
        27 isolead
        28 isotheral
        29
        30
        31
        32 #  Exercises:
        33 #  ---------
        34 #  Modify this script to take the LETTERSET as a command-line parameter.
        35 #  Parameterize the filters in lines 11 - 13 (as with $FILTER),
        36 #+ so that they can be specified by passing arguments to a function.
        37
        38 #  For a slightly different approach to anagramming,
        39 #+ see the agram2.sh script.

  See also Example 29-4, Example 16-25, and Example A-9.

- Use "anonymous here documents" to comment out blocks of code, to save having to individually comment out each line with a #. See Example 19-11.

- Running a script on a machine that relies on a command that might not be installed is dangerous. Use whatis to avoid potential problems with this.

         1 CMD=command1                 # First choice.
         2 PlanB=command2               # Fallback option.
         3
         4 command_test=$(whatis "$CMD" | grep 'nothing appropriate')
         5 #  If 'command1' not found on system , 'whatis' will return
         6 #+ "command1: nothing appropriate."
         7 #
         8 #  A safer alternative is:
         9 #     command_test=$(whereis "$CMD" | grep \/)
        10 #  But then the sense of the following test would have to be reversed,
        11 #+ since the $command_test variable holds content only if
        12 #+ the $CMD exists on the system.
        13 #     (Thanks, bojster.)
        14
        15
        16 if [[ -z "$command_test" ]]  # Check whether command present.
        17 then
        18   $CMD option1 option2       #  Run command1 with options.
        19 else                         #  Otherwise,
        20   $PlanB                     #+ run command2.
        21 fi

- An if-grep test may not return expected results in an error case, when text is output to `stderr`, rather that `stdout`.

         1 if ls -l nonexistent_filename | grep -q 'No such file or directory'
         2   then echo "File \"nonexistent_filename\" does not exist."
         3 fi

  Redirecting `stderr` to `stdout` fixes this.

         1 if ls -l nonexistent_filename 2>&1 | grep -q 'No such file or directory'
         2 #                             ^^^^
         3   then echo "File \"nonexistent_filename\" does not exist."
         4 fi
         5
         6 # Thanks, Chris Martin, for pointing this out.

- If you absolutely must access a subshell variable outside the subshell, here's a way to do it.

         1 TMPFILE=tmpfile                  # Create a temp file to store the variable.
         2
         3 (   # Inside the subshell ...
         4 inner_variable=Inner
         5 echo $inner_variable
         6 echo $inner_variable >>$TMPFILE  # Append to temp file.
         7 )
         8
         9     # Outside the subshell ...
        10
        11 echo; echo "-----"; echo
        12 echo $inner_variable             # Null, as expected.
        13 echo "-----"; echo
        14
        15 # Now ...
        16 read inner_variable <$TMPFILE    # Read back shell variable.
        17 rm -f "$TMPFILE"                 # Get rid of temp file.
        18 echo "$inner_variable"           # It's an ugly kludge, but it works.

- The run-parts command is handy for running a set of command scripts in a particular sequence, especially in combination with cron or at.

- For doing multiple revisions on a complex script, use the rcs Revision Control System package.

  Among other benefits of this is automatically updated ID header tags. The co command in rcs does a parameter replacement of certain reserved key words, for example, replacing `# $Id$` in a script with something like:

         1 # $Id: hello-world.sh,v 1.1 2004/10/16 02:43:05 bozo Exp $

## 36.7.2. Widgets

It would be nice to be able to invoke X-Windows widgets from a shell script. There happen to exist several packages that purport to do so, namely Xscript, Xmenu, and widtools. The first two of these no longer seem to be maintained. Fortunately, it is still possible to obtain widtools here.

| The widtools (widget tools) package requires the XForms library to be installed. Additionally, the Makefile needs some judicious editing before the package will build on a typical Linux system. Finally, three of the six widgets offered do not work (and, in fact, segfault).

The dialog family of tools offers a method of calling "dialog" widgets from a shell script. The original dialog utility works in a text console, but its successors, gdialog, Xdialog, and kdialog use X-Windows-based widget sets.

Example 36-22. Widgets invoked from a shell script

       1 #!/bin/bash
       2 # dialog.sh: Using 'gdialog' widgets.
       3
       4 # Must have 'gdialog' installed on your system to run this script.
       5 # Or, you can replace all instance of 'gdialog' below with 'kdialog' ...
       6 # Version 1.1 (corrected 04/05/05)
       7
       8 # This script was inspired by the following article.
       9 #     "Scripting for X Productivity," by Marco Fioretti,
      10 #      LINUX JOURNAL, Issue 113, September 2003, pp. 86-9.
      11 # Thank you, all you good people at LJ.
      12
      13
      14 # Input error in dialog box.
      15 E_INPUT=85
      16 # Dimensions of display, input widgets.
      17 HEIGHT=50
      18 WIDTH=60
      19
      20 # Output file name (constructed out of script name).
      21 OUTFILE=$0.output
      22
      23 # Display this script in a text widget.
      24 gdialog --title "Displaying: $0" --textbox $0 $HEIGHT $WIDTH
      25
      26
      27
      28 # Now, we'll try saving input in a file.
      29 echo -n "VARIABLE=" > $OUTFILE
      30 gdialog --title "User Input" --inputbox "Enter variable, please:" \
      31 $HEIGHT $WIDTH 2>> $OUTFILE
      32
      33
      34 if [ "$?" -eq 0 ]
      35 # It's good practice to check exit status.
      36 then
      37   echo "Executed \"dialog box\" without errors."
      38 else
      39   echo "Error(s) in \"dialog box\" execution."
      40         # Or, clicked on "Cancel", instead of "OK" button.
      41   rm $OUTFILE
      42   exit $E_INPUT
      43 fi
      44
      45
      46
      47 # Now, we'll retrieve and display the saved variable.
      48 . $OUTFILE   # 'Source' the saved file.
      49 echo "The variable input in the \"input box\" was: "$VARIABLE""
      50
      51
      52 rm $OUTFILE  # Clean up by removing the temp file.
      53              # Some applications may need to retain this file.
      54
      55 exit $?
      56
      57 # Exercise: Rewrite this script using the 'zenity' widget set.

The xmessage command is a simple method of popping up a message/query window. For example:

       1 xmessage Fatal error in script! -button exit

The latest entry in the widget sweepstakes is zenity. This utility pops up GTK+ dialog widgets-and-windows, and it works very nicely within a script.

       1 get_info ()
       2 {
       3   zenity --entry       #  Pops up query window . . .
       4                        #+ and prints user entry to stdout.
       5
       6                        #  Also try the --calendar and --scale options.
       7 }
       8
       9 answer=$( get_info )   #  Capture stdout in $answer variable.
      10
      11 echo "User entered: "$answer""

For other methods of scripting with widgets, try Tk or wish (Tcl derivatives), PerlTk (Perl with Tk extensions), tksh (ksh with Tk extensions), XForms4Perl (Perl with XForms extensions), Gtk-Perl (Perl with Gtk extensions), or PyQt (Python with Qt extensions).

Prev | Home | Next
Optimizations | Up | Security Issues