tcl insert string at certain line in file - insert

I want to insert one string at the certain line ,and I know the line number.
ex.
#Aa_version = Aa/45.21-a32_1
#Aa_version = Aa/47.21-a33_1
Aa_version = Aa/45.27-a57_2 ->I can get this line number n
and I want to insert one line Aa/49.27-a54_1 at line n+1
and put Aa_version = Aa/45.27-a57_2 -> #Aa_version = Aa/45.27-a57_2
output is like
#Aa_version = Aa/45.21-a32_1
#Aa_version = Aa/47.21-a33_1
#Aa_version = Aa/45.27-a57_2
Aa_version = Aa/49.27-a54_1
and my code is
set Aa ""
set fp [open $file "r+"]
set lines [split [read $fp] \n]
set idx [lsearch -regexp $lines {^Aa_version} ]
regexp {Aa(.+)} [lindex $lines $idx] Aa_version
set old_version "#$Aa_version"
set newAa [gets stdin]
set new_version "Aa_version =$newAa "
puts $old_version ->replace $Aa_version
puts $new_version
close $fp
How can I put them at the correct line
thanks

I think it's easier and cleaner to handle the input file a line at a time and look at each one in turn instead of reading it all in one single go and then finding, altering and inserting elements in a list:
set fp [open $file]
while {[gets $fp line] >= 0} {
# If the line starts with Aa_version...
if {[string match "Aa_version*" $line]} {
# Comment it out
puts "#$line"
# And read the new version and write it out
set newAa [gets stdin]
puts "Aa_version = $newAa"
# Copy the rest of the file to standard output and exit the loop
chan copy $fp stdout
break
} else {
puts $line
}
}
close $fp
But if you want to keep the current list based approach, lreplace is your friend:
set fp [open $file]
set lines [split [read $fp] \n]
close $fp
set idx [lsearch -glob $lines "Aa_version*"]
# If a match was found...
if {$idx >= 0} {
# Read the new verson
set newAa [gets stdin]
# Replace the version element with two new elements:
# Commented out previous version, and new version
set lines [lreplace $lines $idx $idx \
"#[lindex $lines $idx]" "Aa_version = $newAa"]
}
puts [join $lines \n]

Related

Iteration in TCL using for each

I'm trying to iterate the value using for each in TCL
This how i give input
./a.sh value1,value2
in the a.sh script
set var [lindex $argv 0]
set values [split $var ","]
foreach {set i 0} {$values} {
puts "iterated once $i"
}
i want the iteration to happen twice since there are two values passed . but instead it is iterating only once
please help me on this
thanks in advance
The foreach command, in its simplest form, takes a variable name, a list value, and a script to run for each element of the list. What you were passing was… weird in several ways at once. Look, here's a correct way of doing it:
set values [split $var ","]
foreach item $values {
puts "iterated: $item"
}
If you want to count through, you should set up your own counter:
set values [split $var ","]
foreach item $values {
set i [incr counter]
puts "iterated #$i: $item"
}
That can be shortened to this:
foreach item [split $var ","] {
puts "iterated #[incr counter]: $item"
}

Open file in Tcl with variables inside

I want to open a file called filelist.txt which contains only the string ${PATHFILE}/test.txt, read the line and open the file test.txt. The file test.txt is present inside the folder ~/testfile.
Consider this sample code:
#!/usr/bin/env tclsh
set PATHFILE "~/testfile"
set fp [open "filelist.txt" r]
set lines [split [read $fp] "\n"]
close $fp
foreach line $lines {
set fp1 [open $line r]
close $fp1
}
The problem is that it seems that the "open" command cannot find the PATHFILE variable and i get this error:
couldn't open "${PATHFILE}/test.txt": no such file or directory
If i try to open the file with set fp1 [open "${PATHFILE}/test.txt" r] i don't have any errors.
Yes, you can use the TCL subst command to evaluate the PATHFILE variable. Note that you might still have an issue with the tilde ~ - it may be better to use full path names.
#!/usr/bin/env tclsh
set PATHFILE "~/testfile"
set fp [open "filelist.txt" r]
set lines [split [read $fp] "\n"]
close $fp
foreach line $lines {
set fp1 [open [subst $line] r]
close $fp1
}

wrong # args: should be "foreach varList list ?varList list ...? command"

this is the .txt file
desk-12
desk-123
desk-auto-1234
this is the .expect file
#!/usr/bin/expect
set f [open "listOfIps.txt"]
set ips [split [read $f] "\n"]
close $f
set PASSWORD "test#123"
puts "$ips"
foreach HOST $ips{
expect -> "
puts $HOST
#spawn scp -r /usr/bin/scp /Users/test-123/1.png admin#$HOST:/home/testFolder
expect {
"*password:*"
{ send $PASSWORD\r}
}
}
puts "completed"
can anyone help me how to solve this "wrong # args: should be "foreach varList list ?varList list ...? command"" error
On the line
foreach HOST $ips{
you need to add a space between $ips and { for Tcl to parse this correctly.

How to store a line from expect_out(buffer) to a variable in tcl

For example, if the output ( in expect_out(buffer) )is
blah
blh blah
asdjsudfsdf
how can I store the 2nd line to a variable? so far I have this:
foreach line [split $expect_out(buffer) "\n"] {
if [lindex $line 1] {
set variable $line
}
}
But this does not work, it says the variable variable is undefined. I tried adding a counter, but that didn't work either. There has to be an easier way!
yes there is an easier way:
set lines [split $expect_out(buffer) \n]
set variable [lindex $lines 1]
or in one line
set variable [lindex [split $expect_out(buffer) \n] 1]
Keep mindful you know what Tcl commands return: split returns a list. You then use lindex to find the 2nd element of the list.

How to have expect timeout when trying to login to an ssh session it has spawned?

I am writing an bash script that uses expect to login to a bunch of Cisco ASAs (they don't support certificate login, hence using expect), makes a change to the configuration and then logs out.
I'd like the script to move onto the next ASA if it is unable to login.
Here is the script:
#!/bin/bash
# Scriptname: set-mtu
for asa in $(cat asa-list-temp)
do
/usr/bin/expect << EndExpect
spawn ssh admin_15#$asa
expect "assword:"
send "pa$$w0rd\r"
expect ">"
send "do something\r"
expect ">"
send "exit\r"
EndExpect
done
I think I can set a timeout on expect "assword:" but I can't figure out how to get it to close the spawned ssh session and then move onto the next ASA in the for list.
First of all I would use an expect script for this and lose the bash scripting.
Then for the expect part:
You can do this by using a switch that also matches for timeout (page 12 of exploring expect). In that way you can explicitly have some action when expect timeouts.
Otherwise by setting the timeout it will just continue with the next command in line.
set timeout 60
expect {
"assword:" {
}
timeout {
exit 1 # to exit the expect part of the script
}
}
I've created something similar where I used an overall expect script to run an expect script in parallel.
multiple.exp
#!/bin/sh
# the next line restarts using tclsh \
exec expect "$0" "$#"
# multiple.exp --
#
# This file implements the running of multiple expect scripts in parallel.
# It has some settings that can be found in multiple.config
#
# Copyright (c) 2008
#
# Author: Sander van Knippenberg
#####
# Setting the variables
##
source [file dirname $argv0]/.multiple.config
# To determine how long the script runs
set timingInfo("MultipleProcesses") [clock seconds]
# ---------------------------------------------------------------------
######
# Procedure to open a file with a certain filename and retrieve the contents as a string
#
# Input: filename
# Output/Returns: content of the file
##
proc openFile {fileName} {
if {[file exists $fileName] } {
set input [open $fileName r]
} else {
puts stderr "fileToList cannot open $fileName"
exit 1
}
set contents [read $input]
close $input
return $contents
}
######
# Procedure to write text to a file with the given filename
#
# Input: string, filename
##
proc toFile {text filename} {
# Open the filename for writing
set fileId [open $filename "w"]
# Send the text to the file.
# Failure to add '-nonewline' will reslt in an extra newline at the end of the file.
puts -nonewline $fileId $text
# Close the file, ensuring the data is written out before continueing with processing
close $fileId
}
# ---------------------------------------------------------------------
# Check for the right argument
if {$argc > 0 } {
set hostfile [lindex $argv 0]
} else {
puts stderr "$argv0 --- usage: $argv0 <hosts file>"
exit 1
}
# Create the commands that can be spawned in parallel
set commands {}
# Open the file with devices
set hosts [split [openFile $hostfile] "\n"]
foreach host $hosts {
if { [string length $host] > 1 } {
lappend commands "$commandDir/$commandName $host" # Here you can enter your own command!
}
}
# Run the processes in parallel
set idlist {}
set runningcount 0
set pattern "This will never match I guess"
# Startup the first round of processes until maxSpawn is reached,
# or the commands list is empty.
while { [llength $idlist] < $maxSpawn && [llength $commands] > 0} {
set command [lindex $commands 0]
eval spawn $command
lappend idlist $spawn_id
set commands [lreplace $commands 0 0]
incr runningcount
set commandInfo($spawn_id) $command
set timingInfo($spawn_id) [clock seconds]
send_user " $commandInfo($spawn_id) - started\n"
}
# Finally start running the processes
while {$runningcount > 0} {
expect {
-i $idlist $pattern {
}
eof {
set endedID $expect_out(spawn_id)
set donepos [lsearch $idlist $endedID]
set idlist [lreplace $idlist $donepos $donepos]
incr runningcount -1
set elapsedTime [clock format [expr [clock seconds] - $timingInfo($endedID)] -format "%M:%S (MM:SS)"]
send_user " $commandInfo($endedID) - finished in: $elapsedTime\n"
# If there are more commands to execute then do it!
if {[llength $commands] > 0} {
set command [lindex $commands 0]
eval spawn $command
lappend idlist $spawn_id
set commands [lreplace $commands 0 0]
incr runningcount
set commandInfo($spawn_id) $command
set timingInfo($spawn_id) [clock seconds]
}
}
timeout {
break
}
}
}
set elapsed_time [clock format [expr [clock seconds] - $timingInfo("MultipleProcesses")] -format "%M:%S (MM:SS)"]
send_user "$argv0 $argc - finished in: $elapsedTime\n"
multiple.config
# The dir from where the commands are executed.
set commandDir "/home/username/scripts/expect/";
set commandName "somecommand.exp";
# The maximum number of simultanious spawned processes.
set maxSpawn 40;
# The maximum timeout in seconds before any of the processes should be finished in minutes
set timeout 20800;
To make the answer clear: the solution is typical, just make timeout treatment inside the curly braced expect notation. So, your Tcl/Expect part in the shell script should be:
spawn ssh user#host
expect {
"assword:" {
send "password\r"
}
timeout {
exit
}
}
expect "prompt>"
. . .
login success
. . .
Here is another example, how to expect/treat timeout to resume waiting for the result string while the spawned command is still running.

Resources