Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Converting large bash script to TCL

207 views
Skip to first unread message

al-s

unread,
Feb 15, 2006, 10:16:12 AM2/15/06
to
Hello all,
I know that TCL and shell scripting share a lot of similarities.
Nevertheless, is there a way to convert bash scripts or any other shell
interpreter for that matter into equivalent TCL code. The shell script
is pretty huge and to convert it line by line is going to take quite a
while to do.

Thanks in advance for any help

-Al

Uwe Klein

unread,
Feb 15, 2006, 12:31:36 PM2/15/06
to
If I where You I would not do that.

You would wrap the contortions done in shell
with more contortions to make tcl behave like shell.

Determine what it ( the script ) is supposed to do
and write it anew.


What about using this shellscript from tcl by way of [exec]

uwe

al-s

unread,
Feb 15, 2006, 1:46:43 PM2/15/06
to

Hi Uwe,

Thanks for responding!

It's a shell script that is interactive in nature in the form of
prompts. It takes user input of disk drives in a RAID in a single line
ie. sda1 sdb1 sdc1... and writes a large file to each then compares the
file written to the reference. It then deletes the file from each
drive.

If I were to "exec" that in TCL I would have to capture the stdout to a
text box - I'm using wish8.4.

bash script is as follows:

BEGIN BASH SCRIPT
**************************
#!/bin/sh

# Script to test file on raid or jbod storage device

yes="y"
declare -a devices

testscript="RAID/JBOD SCSI file test script - rev A"
echo $testscript

echo "WOULD YOU LIKE TO INSTALL NAC DRIVER (enter y for yes n for no)
THEN ENTER"
read insdriver
if [ $insdriver = $yes ]; then
./install
fi

# ****** GATHER INFORMATION FOR TEST ******

echo "WOULD YOU LIKE TO DO FILE TESTING (enter y for yes n for no) THEN
ENTER"
read filetest

if [ $filetest = $yes ]; then
echo "SPECIFY FILE SIZE TO TEST (100 = 100Mbyte) THEN ENTER"
read filesize
echo "files size to create is $filesize Mbyte"
echo "WOULD YOU LIKE TO COPY A FILE FROM HOST TO STORAGE (enter y for
yes n for no) THEN ENTER"
read filecopy
echo "WOULD YOU LIKE TO DELETE THE FILE FROM STORAGE WHEN TESTING IS
COMPLETE (enter y for yes n for no) THEN ENTER"
read filedelete

# ****** GET STORAGE DEVICES TO WRITE FILE ******

echo "ENTER STORAGE DEVICE NAMES TO RUN THE FILE TEST ON ONE LINE
(ex:sda1 sdb5 sdd6) THEN ENTER"
read -a devices
devicecount=${#devices[@]}
index=0
while [ "$index" -lt "$devicecount" ]
do
echo ${devices[$index]}
let "index = $index +1"
done

# **** clear out test1.txt file ****

cp zero.txt test2.txt

# **** create file of size filesize use testbase.txt to generate
test1.txt

echo "creating test file... this takes a couple of minutes"
while [ $filesize -gt 0 ]
do
cat
testbase2.txt>>/home/root/DDC/latest/FC7901xS1/ddk/applications/test2.txt
filesize=$((filesize-1))
done
echo "test file test2.txt is created"

# **** copy file to RAID or JBOD? ********

if [ $filecopy = $yes ];then
# **** mount storage copy file then sync and compare files
index=0
while [ "$index" -lt "$devicecount" ]
do
echo "mounting ${devices[$index]}"
echo "mounting
${devices[$index]}">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
mount -t ext2 -v /dev/${devices[$index]}
/new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
echo "copying test file to ${devices[$index]}"
cp test2.txt /new_root
echo "sync disks"
sync
echo "comparing files now (no output if files are the same)"
cmp -l test2.txt /new_root/test2.txt
echo "COMPARE COMPLETE for ${devices[$index]}"
echo "unmount ${devices[$index]}"
umount -v /new_root
let "index = $index + 1"
done
fi
echo "***** FILE TEST COMPLETE *****"

# **** delete file from RAID or JBOD? *******

if [ $filedelete = $yes ];then
index=0
while [ "$index" -lt "$devicecount" ]
do
echo "mounting ${devices[$index]}"
echo "mounting
${devices[$index]}">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
mount -t ext2 -v /dev/${devices[$index]}
/new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
echo "deleting file test2.txt from ${devices[$index]}"
rm /new_root/test2.txt
echo "test2.txt is now removed from ${devices[$index]}"
echo "unmount ${devices[$index]}"
umount -v /new_root
let "index = $index + 1"
done
fi

fi

END BASH SCRIPT
*************************

That's the whole of it. This is why I don't want to start from
scratch. If you think it's better to start from scratch upon seeing
this, let me know,

Bryan Oakley

unread,
Feb 15, 2006, 2:17:48 PM2/15/06
to
al-s wrote:

> That's the whole of it. This is why I don't want to start from
> scratch. If you think it's better to start from scratch upon seeing
> this, let me know,
>

Interesting. When someone says "large" to me I think of 10's of
thousands of lines of code. It looks like that script could be rewritten
in under an hour, easily (assuming you know Tcl to begin with). If I
were you, I'd start from scratch.

Besides, if you're creating a GUI there's no reason to prompt the user
sequentially -- give them some widgets and an "OK" or "Execute" button.

--
Bryan Oakley
http://www.tclscripting.com

Uwe Klein

unread,
Feb 15, 2006, 2:18:36 PM2/15/06
to
al-s wrote:

> It's a shell script that is interactive in nature in the form of
> prompts. It takes user input of disk drives in a RAID in a single line
> ie. sda1 sdb1 sdc1... and writes a large file to each then compares the
> file written to the reference. It then deletes the file from each
> drive.
>
> If I were to "exec" that in TCL I would have to capture the stdout to a
> text box - I'm using wish8.4.
>

A question first: do you just aim for having the output
viewable in some gui widget?

and does not look too bad;-)

1. rewrite in tcl:
you will have to [exec]
the system stuff "mount .."
and be root for that
the yes/no and name stuff
would be handled by entry and checkbox widgets
about 1/3 of your script is putting info
to the user and getting an answer.
The rest seems to be simple repeat loops.


2. wrap the script with expect
try "man expect"
expect is a chat programm on steroids
based on tcl. Actually it is a standard
tclinterpreter with a binary extension
facilitating driving other processes through
a pty
[spawn] your shellscript
and then loop through
[expect] "what the script says"
and answer by [exp_send] "what the script expects"
you can handle all output with tcl and place it
in widgets of your liking.

depending on how you mind meshes with how expect works
the first or the second might seem easier to you.

uwe


--
Uwe Klein [mailto:uwe-...@foni.net]
KLEIN MESSGERAETE Habertwedt 1
D-24376 Groedersby b. Kappeln, GERMANY
phone: +49 4642 920 123 FAX: +49 4642 920 125

al-s

unread,
Feb 15, 2006, 2:40:31 PM2/15/06
to
It depends on one's perspective. If I had my own Tcl website, I might
agree with you.

al-s

unread,
Feb 15, 2006, 2:43:12 PM2/15/06
to
Yes all output will go into GUI text widget. Maybe a tip to get the
array handling worked out and I can utilize the rest of your
suggestions - Thanks again...

Bryan Oakley

unread,
Feb 15, 2006, 2:43:51 PM2/15/06
to
al-s wrote:
> It depends on one's perspective. If I had my own Tcl website, I might
> agree with you.
>

Like I said, "assuming you know Tcl to begin with". If you don't know
Tcl, why do you want to rewrite it?

However, as simple as that script is, I'd wager that even if you don't
know Tcl and Tk, you could probably re-implement that script in less
than a day if you are a professional programmer.

Uwe Klein

unread,
Feb 15, 2006, 2:49:53 PM2/15/06
to
make a start and post it

there are enough eyes in comp.lang.tcl to lead
you into tcl-proficiency.

uwe

bine...@gmail.com

unread,
Feb 16, 2006, 6:03:39 AM2/16/06
to
# Script to test file on raid or jbod storage device

set yes "y"

exec declare -a devices


set testscript "RAID/JBOD SCSI file test script - rev A"
puts $testscript


puts "WOULD YOU LIKE TO INSTALL NAC DRIVER (enter y for yes n for no)
THEN ENTER"
set insdriver [gets stdin]
if {$insdriver == $yes } {
exec ./install
}

# ****** GATHER INFORMATION FOR TEST ******

puts "WOULD YOU LIKE TO DO FILE TESTING (enter y for yes n for no) THEN

ENTER"
set filetest [gets stdin]


if { $filetest == $yes } {
puts "SPECIFY FILE SIZE TO TEST (100 = 100Mbyte) THEN ENTER"
set filesize [gets stdin]
puts "files size to create is $filesize Mbyte"
puts "WOULD YOU LIKE TO COPY A FILE FROM HOST TO STORAGE (enter y for


yes n for no) THEN ENTER"

set filecopy [gets stdin]
puts "WOULD YOU LIKE TO DELETE THE FILE FROM STORAGE WHEN TESTING IS


COMPLETE (enter y for yes n for no) THEN ENTER"

set filedelete [gets stdin]


# ****** GET STORAGE DEVICES TO WRITE FILE ******


puts "ENTER STORAGE DEVICE NAMES TO RUN THE FILE TEST ON ONE LINE


(ex:sda1 sdb5 sdd6) THEN ENTER"

set devices [gets stdin]
set devicecount [llength $devices]
for { set i 0} { $i <= $devicecount} {incr i } {
puts [lindex $devicecount $i]
}


# **** clear out test1.txt file ****


file copy zero.txt test2.txt


# **** create file of size filesize use testbase.txt to generate
test1.txt

set fileId [open
"/home/root/DDC/latest/FC7901xS1/ddk/applications/test2.txt" "a"]

puts "creating test file... this takes a couple of minutes"
while { $filesize > 0} {
puts $fileId testbase2.txt
set filesize [expr {$filesize-1}]
}
puts "test file test2.txt is created"


# **** copy file to RAID or JBOD? ********


if {$filecopy == $yes} {


# **** mount storage copy file then sync and compare files

for { set i 0} { $i <= $devicecount} {incr i } {
set devicename [lindex $devices $i]
puts "mounting $devicename"
eval exec { echo "mounting

$devicename">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt

mount -t ext2 -v /dev/${devices[$index]}

/new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
}
puts "copying test file to $devicename"
file copy test2.txt /new_root
puts "sync disks"
exec sync
puts "comparing files now (no output if files are the same)"
exec cmp -l test2.txt /new_root/test2.txt
puts "COMPARE COMPLETE for $devicename"
puts "unmount $devicename"
exec umount -v /new_root
}
}
puts "***** FILE TEST COMPLETE *****"


# **** delete file from RAID or JBOD? *******

if {$filedelete == $yes} {

for { set i 0} { $i <= $devicecount} {incr i } {
set devicename [lindex $devices $i]
puts "mounting $devicename"
puts "mounting $devicename
>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt "
eval exec { echo "mounting

$devicename">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt

mount -t ext2 -v /dev/${devices[$index]}

/new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
}
puts "deleting file test2.txt from $devicename"
file delete /new_root/test2.txt
puts "test2.txt is now removed from $devicename"
puts "unmount $devicename"
exec umount -v /new_root

Uwe Klein

unread,
Feb 16, 2006, 6:21:38 AM2/16/06
to
bine...@gmail.com wrote:
> # Script to test file on raid or jbod storage device
>
> set yes "y"
>
# not needed and would not work because the context
# of the subshell is lost after exec
> exec declare -a devices

devices is later used as list (and inside of tcl only).
>
>

uwe

Ralf Fassel

unread,
Feb 16, 2006, 8:50:49 AM2/16/06
to
* bine...@gmail.com

| set devices [gets stdin]
| set devicecount [llength $devices]

If the user enters anything which is not a proper tcl list, this line
will trigger an TCL error. Better use something along the line of:

set devicelist [list]
foreach item [split [gets stdin]] {
set dev [string trim $dev]
if {[string length $dev] > 0} {
lappend devicelist $dev
}
}
puts [join $devicelist "\n"]



| puts "creating test file... this takes a couple of minutes"
| while { $filesize > 0} {
| puts $fileId testbase2.txt
| set filesize [expr {$filesize-1}]
| }

This writes the literal string "testbase2.txt" $filesize times into
the fileid. It does not even look at the contents of the
testbase2.txt file. Use
exec cat testbase2.txt >> destination
instead of the 'puts' to get identical functionality of your bash
script.

| eval exec { echo "mounting
|
| $devicename">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt
|
| mount -t ext2 -v /dev/${devices[$index]}
|
| /new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
| }

- avoid 'eval exec' unless you know what you're doing
- indexing a list is done via lindex, not via [].

set fd [open /home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt a+]
puts $fd "mounting $devicename"
close $fd
exec mount -t ext2 -v /dev/[lindex $devicelist $i]

As an alternative to


for { set i 0} { $i <= $devicecount} {incr i } {

... your code ... [lindex $devicelist $index]
}
you could simply use
foreach dev $devicelist {
... your code ... $dev
}

| puts "comparing files now (no output if files are the same)"
| exec cmp -l test2.txt /new_root/test2.txt

You need to 'catch' this since cmp will exit non-zero when the files
differ, otherwise your script stops here.

if {[catch {exec cmp -l test2.txt /new_root/test2.txt} msg]} {
puts stderr $msg
}

| eval exec { echo "mounting
|
| $devicename">>/home/root/DDC/latest/FC7901xS1/ddk/applications/filet­est.txt
|
| mount -t ext2 -v /dev/${devices[$index]}
|
| /new_root>>/home/root/DDC/latest/FC7901xS1/ddk/applications/filetest.txt
| }

See above.

HTH
R'

al-s

unread,
Feb 16, 2006, 9:41:49 AM2/16/06
to
Thanks, I am grateful for you taking the time for a serious kick start.

-Al

al-s

unread,
Feb 16, 2006, 11:22:45 AM2/16/06
to
Hi Ralf,

I tried the mod you suggested and get the following from the error log

can't read "dev": no such variable
can't read "dev": no such variable
while executing
"string trim $dev"
("foreach" body line 2)
invoked from within


"foreach item [split [gets stdin]] {
set dev [string trim $dev]
if {[string length $dev] > 0} {

lappend devices $dev
}
}"

Looks like dev is not in scope. Is this intedded to work with a STDIN
entry like "sda1 sdb1 sdc1" ? This is how the devices would be
entered. The space will have to be trimmed and each device will have
to be an entry that's accessible in code. Would an array work or is
this better?

Thanks,

-Al

Juan C. Gil

unread,
Feb 16, 2006, 11:37:03 AM2/16/06
to
That's what makes comp.lang.tcl so special among comp.lang.*

Juan Carlos---

al-s

unread,
Feb 16, 2006, 12:13:49 PM2/16/06
to
I tried putting the few lines in a mini script:

set devicelist [list]
foreach item [split [gets stdin]] {
set dev [string trim $dev]
if {[string length $dev] > 0} {
lappend devicelist $dev
}
}
puts [join $devicelist "\n"]

I still get a problem with cant read dev: no such variable. I don't
get it. This is so very basic. I'm sure I just need fresh (and more
experienced) eyes on this

Uwe Klein

unread,
Feb 16, 2006, 12:27:19 PM2/16/06
to
al-s wrote:
> I tried putting the few lines in a mini script:
>
> set devicelist [list]
> foreach item [split [gets stdin]] {
> set dev [string trim $item]
---------------------------^^^^^^^^^

> if {[string length $dev] > 0} {
> lappend devicelist $dev
> }
> }
> puts [join $devicelist "\n"]
>
> I still get a problem with cant read dev: no such variable. I don't
> get it. This is so very basic. I'm sure I just need fresh (and more
> experienced) eyes on this
>

uwe

Ralf Fassel

unread,
Feb 16, 2006, 1:03:35 PM2/16/06
to
* "al-s" <stos...@hotmail.com>

| "foreach item [split [gets stdin]] {
| set dev [string trim $dev]

Sorry, that line should not have been there any more. The 'string
trim' is unneccessary here, since after 'split' the individual
elements should not have leading or trailing whitespace... after all,
that's what 'split' is there for :-)



| Is this intedded to work with a STDIN entry like "sda1 sdb1 sdc1" ?

Yes. This splits the line by whitespace and checks each entry. Note
that tcl 'split' returns an empty list element when successive spaces
are entered, so you have to handle those.

Corrected loop would be

set devices [list]
foreach dev [split [gets stdin]] {
# ignore empty list elements


if {[string length $dev] > 0} {
lappend devices $dev
}
}

Probably you would have to check after that loop whether any devices
were entered:
if {[llength $devices] == 0} {
# no devices entered
}

R'

0 new messages