TCL/Expect variable vs $variable - variables

Which one is proper way of using variable with or without dollar sign? I thought that variable (without $) is used only during variable declaration (similar to Bash):
set var 10
In all other cases when variable is referred or used (but not declared) the proper syntax is $variable (with $):
set newVar $var
puts $var
puts $newVar
But then I found code where it is interchanged and seems that this code is working:
# using argv
if {[array exists argv]} {
puts "argv IS ARRAY"
} else {
puts "argv IS NOT AN ARRAY"
}
# using $argv
if {[array exists $argv]} {
puts "\$argv IS ARRAY"
} else {
puts "\$argv IS NOT AN ARRAY"
}
# using argv
if {[string is list argv]} {
puts "argv IS LIST"
} else {
puts "argv IS NOT LIST"
}
# using $argv
if {[string is list $argv]} {
puts "\$argv IS LIST"
} else {
puts "\$argv IS NOT LIST"
}
Output:
argv IS NOT AN ARRAY
$argv IS NOT AN ARRAY
argv IS LIST
$argv IS LIST
Edit in reply to #glenn jackman:
Your reply pointed me to further research and I've found that TCL is capable doing some sort of "self modifying code" or whatever is correct name e.g.:
% set variableName "x"
x
% puts $x
can't read "x": no such variable
% set $variableName "abc"
abc
% puts $x
abc
% puts [set $variableName]
abc
%
%
%
%
%
%
% set x "def"
def
% puts $x
def
% puts [set $variableName]
def
%
Now your answer bring some light to problem, but one question remains. This is excerpt from documentation:
set varName ?value?
array exists arrayName
Documentation says that both functions expect variable name (not value) in other words it expect variable instead of $variable. So I assume (based on above self modifying code) that when I pass $variable instead of variable the variable substitution took place (exactly the same as code above). But what if $variable contains something that is not a list neither array (my arguments during testing was: param0 param1 "param 2" param3). From this point of view the output that says $argv IS LIST is wrong. What am I missing here?
Edit in reply to #schlenk:
Finally I (hope) understand the problematic. I've found great article about TCL, which explain (not just) this problematic. Let me pinpoint a few wise statement from this article:
In Tcl what a string represents is up to the command that's
manipulating it.
Everything is a command in Tcl - as you can see there is no
assignment operator.
if is a command, with two arguments.
The command name is not a special type but just a string.
Also following SO answer confirms this statement:
"In Tcl, values don't have a type... they question is whether they can be used as a given type."
The command string is integer $a means:
"Can I use the value in $a as an integer"
NOT
"Is the value in $a an integer"
"Every integer is also a valid list (of one element)... so it can be
used as either and both string is commands will return true (as will
several others for an integer)."
I believe the same applies also for string is list command:
% set abc "asdasd"
asdasd
% string is list $abc
1
% string is alnum $abc
1
string is list returns 1 because $abc is string and also it is one element list etc. In most tutorials there are said that following snippet is the proper way of declaring and working with lists:
% set list1 { 1 2 3 }
% lindex $list1 end-1
2
But when everything in TCL is string the following is also working in my experience (if I am wrong correct me please).
% set list2 "1 2 3"
1 2 3
% lindex $list2 end-1
2

It depends on the command. Some Tcl commands require a variable name as a parameter, if they need to modify the contents of the variable. Some are:
set
foreach
lappend
incr
Most but certainly not all commands want to take a variable's value.
You'll need to check the documentation for the relevant commands to see if the parameters include "varName" (or "dictionaryVariable"), or if the parameters are named as "string", "list", etc
An example using info exists which takes a varName argument:
% set argv {foo bar baz}
foo bar baz
% info exists argv ;# yes the variable "argv" exists
1
% info exists $argv ;# no variable named "foo bar baz"
0
% set {foo bar baz} "a value" ;# create a variable named "foo bar baz"
a value
% info exists $argv ;# so now that variable exists
1

The important thing to know is that $x in Tcl is just syntactical sugar for the command set x. So you can translate any $x in Tcl code into [set x] in the same place to see what really happens.
The other important thing to consider is immutable values. Tcl values are immutable, so you cannot change them. You can just create a new changed value. But you can change the value stored inside a variable.
This is where the difference between commands taking a variable name and those that take a value comes in. If a command wants to change the value stored in a variable, it takes a variable name. Examples are lappend, lset, append and so on. Other commands return a new value and take a value as argument, examples include lsort, lsearch, lindex.
Another important point is the fact that you do not really have a list type. You have strings that look like lists. So that is what Tcl's string is list tests. This has some consequences, e.g. you cannot always decide if you have a string literal or a one item list, as it is often the same. Example given:
% set maybe_list a
% string is list $maybe_list
1
Combine that with Tcls nearly unrestricted names for variables, as already demonstracted by Glenn and you can get really confused. For example, these are all valid Tcl variable names, you just cannot use all of them with the $ shortcut:
% set "" 1 ;# variable name is the empty string
1
% puts [set ""]
% set " " 1 ;# variable name is just whitespace
1
% puts [set " "]
1
% set {this is a list as variable name} 1 ;# a variable with a list name
1
% puts [set {this is a list as variable name}]
1
% set Δx 1
1
% incr Δx
2
% puts [set Δx]
2

Related

How to unset variables after use in .zshrc?

So let's say I set a bunch of variables in my .zshrc:
function print_color () {
echo -ne "%{\e[38;05;${1}m%}";
}
# color guide: http://misc.flogisoft.com/_media/bash/colors_format/256-colors.sh.png
RED=`print_color 160`
DEFAULT="%{$fg[default]%}"
PROMPT='$RED%${DEFAULT} $ '
The problem is these remain set, once I am actually running commands
$ echo $RED
%{%} # With colors, etc.
How would I unset them after use in my .zshrc? What is the best practice here?
Thanks,
Kevin
Unsetting a parameter is done with the built-in unset
unset PARAMETER
unsets PARAMETER. This can be done as soon as you no longer need PARAMETER, at the end of the file or anywhere inbetween.
"Best practice" depends highly on the use case:
In the case of colors you probably want them to be available from top to bottom, so that you can use the same colors in the whole file. Therefore you probably want to unset them near bottom of ~/.zshrc.
If you are just storing some output for a few lines of code, which you do not need in any other part of the file, you can unset it immediately after the last usage or at the end of the block of code in question.
Also, if you need a parameter only inside a function, you can declare it with local
function foo () {
local PARAMETER
# [...]
}
It will then be only available inside this function without the need for unset.
That being said, in this case you actually need RED to be available in your running shell as it is needed each time your prompt is evaluated (everytime just before it is printed). This is due to PROMPT being defined in single quotes (and the shell option PROMPT_SUBST being set, see output of setopt | grep promptsubst).
If you do not intend to change RED during runtime just put it in double quotes when defining PROMPT:
PROMPT="$RED"'%${DEFAULT} $ '
This will substitute $RED when PROMPT is defined and only ${DEFAULT} each time PROMPT is evaluated.
After that you can just do unset RED. Note that there must be no $ before RED, else the shell would substitute it by the value of RED and try to unset a parameter named like the value:
% FOO=BAR ; BAR=X
% echo "> $FOO | $BAR <"
> BAR | X <
% unset $FOO
% echo "> $FOO | $BAR <"
> BAR | <
% unset FOO
% echo "> $FOO | $BAR <"
> | <
A possibly better solution than unsetting variables is to make them local in a function, so that if they were in the environment at the shell startup, then wouldn't be lost:
putprompt()
{
local RED=`print_color 160` DEFAULT="%{$fg[default]%}"
PROMPT="$RED%${DEFAULT} \$ "
}
then just execute the putprompt function.
Just put unset RED at the end of .zshrc.

how to make a variable global inside a while loop

Following is the TCL script to print numbers between 1 to 10 using while loop.
set b 1
while {$b<11} {
puts $b
incr b
}
In the above script, how to make "puts $b" output as global. So that we can use this where ever we want in the script?
I need the following:
set b 1
while {$b<11} {
set a $b
incr b
}
puts "This is number $a"
If I use $a in outside loop, it should print the output as :
This is number 1
This is number 2
This is number 3
.
.
.
This is number 10
Tcl is really strictly operational; it does things at the point where you tell it to. However, one of the things that you can do is to put a trace on a variable so that some code gets run whenever the variable is written to.
# This is Tcl 8.5 syntax; let me know if you want it for 8.4 and before
trace add variable ::a write {apply {args {
puts "This is number $::a"
}}}
I've used fully-qualified variable names above; the trace is really on the variable a in the namespace ::.
Then, after setting the trace, when we do:
set b 1
while {$b<11} {
set a $b
incr b
}
The output then is:
This is number 1
This is number 2
This is number 3
This is number 4
This is number 5
This is number 6
This is number 7
This is number 8
This is number 9
This is number 10
Which is what you wanted.
Your question isn't entirely clear: I think Donal Fellows may have given you the right answer, but I think I may see another question lurking in your text, namely: how can I write a generic command that will, as it were, take a variable for a brief spin?
As in:
set b 1
myLoop b {
set a $b
puts "This is number $a"
}
puts "Again, this is number $a"
You would write myLoop like this:
proc myLoop {varName body} {
upvar 1 $varName i
for {} {$i < 11} {incr i} {
uplevel 1 $body
}
}
Note that this is not the best way to write a command like this: I'm writing it this way to accommodate your example code.
The command works by calling uplevel to evaluate the script in body in the context of the caller, whichever that is. To allow myLoop to manipulate the variable in the script, we need to set up things so that the command will share the variable with the caller. The upvar command does that.

Nesting if statements inside a while loop - C shell

I have to write a tcsh script for unix that pulls values from every other line in a text file, compares them and decides if you should buy(1), sell(-1) or do nothing(0). Basically a simple stock profit calculation. I think I have all the logic right, but when I run the script I get a "while syntax error" and it never executes. I have the full script below, is it not possible to nest statements in a while loop with unix? If so any suggestions how to do this?
#!/bin/tcsh
set lineNum='wc -l testcase.txt'
set i=1
while ($i<$lineNum)
set prices='sed -n '$lineNump' testcase.txt'
set arr=( $price )
set j='echo ${#arr}'
set price=0
set x=0
set y=0
set k=0
while ($k < $j)
set a=arr[$k]
set str=""
if ($a>$price)
then
str="$str 1"
price=$((price-a))
else if($a<$price)
then
str="$str -1"
price=$((price+a))
else if($a==$price)
then
str="$str 0"
fi
str="$str $price"
if ($str=='sed -n'('expr $lineNum+1'p)' testcase.txt')
then
x=$((x+1))
fi
y=$((y+1))
end
lineNum=$((lineNum+2))
end
echo $x/$y
Your script appears to be a mixture of tcsh and bash syntax.
As Mark's answer says, the then keyword has to be on the same line as the if (unless you use a backslash to splice two lines, but there's not much point in doing that).
For a variable assignment, the set keyword is not optional; this:
str="$str 1"
is a syntax error in csh/tcsh (it will probably look for a command whose name starts with "str=".) Write that as:
set str = "$str 1"
Note that you can optionally have spaces around the = in a set. tcsh's syntax is a bit messed up:
set foo=bar # ok
set foo = bar # ok
set foo= bar # ok
set foo =bar # error: "Variable name must begin with a letter."
The x=$((x+1)) syntax is specific to bash and related shells. tcsh uses # for arithmetic assignments:
set x = 42
# x ++ # sets $x to 43
# x = $x * 2 # sets $x to 86
If you have a choice, I suggest writing your script to use bash rather than tcsh (you're about halfway there already). Its syntax is much more regular.
The classic rant about csh/tcsh programming can be found here.
You are missing the end statement correspoding to the first while.
You are also using fi instead of endif.
The "then" keywords need to be on the same line as the "if" they belong to.

Windows Batch loop all variables with findstr

In my Windows batch file I have a various amount of variables. Lets say I have the following variables:
set varTest1=test1
set varTest2=test2
set otherVar=variable500
set varS=string
set yetAnotherVar=foo
They do really make no sense buts thats not the point. I am looking for a method that prints out all values of variables that start with var:
So when I run my batch with a certain help parameter it should print out all three variables starting with var and its value.
The output could look like this:
These are the available variables:
varTest1 : test1
varTest2 : test2
varS : string
I created the following for reading the parameter:
IF "%1" == "" (
echo No help parameter was set. Program will exit. ) ELSE (
IF "%1" == "help" (
call :showAllAvailableVars ) ELSE (
echo Do something else))
Now I would have my method
:showAllAvailableVars
I think the solution could be something with the findstr method but I could not figure it out how to do that because findstr is mainly for files and not for searching through own program variables.
Create array instead of different variables. Like,
set var[0]=test1
set var[1]=test2
set var[2]=string
then in your 'showAllAvailableVars' function do this
for /L %%i in (1,1,%n%) do echo !var[%%i]!
You could use set var to print all variables which begins with var.
See also set /?

in tcl, can't read a variable when its name is comprised of another variable

Basically, what I'm doing is
set i 0
set log_$i "blah blah"
puts $log_$i; # expecting to see "blah blah"
this returns the error:
can't read "log_": no such variable
I've tried all different kinds of grouping, nothing seems to work
The issue you've got is that $-substitution stops when it encounters a $ (and lots of other punctuation too).
To make what you're doing work, you'd do this to read the variable (using the single-argument form of the set command):
puts [set log_$i]
That compiles to exactly the sort of bytecode that you'd expect.
BUT…
Don't do it that way if you can avoid it.
Any time you're thinking about constructing variables like that, you're more likely to be in need of using arrays:
set i 0
set log($i) "blah blah"
puts $log($i)
That does work. And if you're really in need of working with a variable whose name is constructed, it's often easier to construct a (typically local) variable alias to it like this:
set i 0
upvar 0 log_$i v
set v "blah blah"
puts $v
The upvar command is tricky stuff, and allows for all sorts of really powerful techniques. It also makes the local aliases to variables very efficient (though this alias does not include looking up the variable each time; you'll need to rerun upvar if you want the alias to point to something else).
Another way of doing is
==>tclsh
% set i 0
0
% set log[set i] bb
bb
% puts "[set log[set i]]"
bb
Another way:
% set i 0
0
% set log(0) "blah blah"
blah blah
% puts $log($i)
blah blah