I am trying to understand passing two variables from one csh script to another csh script. Perform simple arithmatic function then passing the sum variable to the orginal csh script. Then outputing the sum variable to standard output and a file.
script1.sh
#!/bin/csh
setenv num1 3
setenv num2 2
source script2.sh
set total=$sum
echo $total > total.txt
echo $total
script2.sh
#!/bin/csh
set int1=$num1
set int2=$num2
set sum=$int1 + $int2
With 'csh', the 'set' command perform simple (string) assignment, and takes a single word. The assignment 'set sum=$int +$int2' has 2 issues
It does not use a single word
Even the expression was combined into a single word (set sum="$int1 + $int2"), the set does not evaluate expression.
Consider instead using the '#' command which will take an expression
# sum2 = $int1 + $int2
Sde note: For 'csh' script, the common convention is to use a '.csh' suffix. The '.sh' suffix is commonly used to files associated with sh-like shells (sh, bash, ash, ...)
Related
an executable expects command-line parameters in the format:
-varname "[64]"
Need to wrap it as an environment variable so the executable can be launched by another tool, so I tried:
> setenv PARAM '-varname "[64]"'
> echo $PARAM
echo: No match.
I tried all kind of escapes but couldn't find how to enclose the original string into an environment variable.
Must mention that both the inner executable and the wrapper are inflexible in their expectations, e.g. the executable expects the variable as shown and the wrapper expects a string that it associates with an environment variable through 'setenv'.
Any hint?
Thanks!
You are setting it correctly, but the problem is how the shell expands the value of $PARAM.
As you know, a star at the command line is expanded by the shell to all the files in the current directory.
> echo *
The result of shell expansion is subsequently used as the argument to echo.
There are additional wildcard/glob patterns allowed at the command line. Square brackets define character class.
To echo all files containing either a 4 or a 6:
> echo *[46]*
To echo all files named exactly 4 or 6.
> echo [46]
In the above example, if no files are named exactly 4 or 6, then you'll get echo: No match.
Solution: use printenv
> setenv PARAM '-varname "[64]"'
> printenv PARAM
--> -varname "[64]"
Note that PARAM, not $PARAM is the argument to printenv. This avoid shell expansion of $PARAM.
This question already has answers here:
Assignment of variables with space after the (=) sign?
(4 answers)
Closed 3 years ago.
#!/bin/bash
declare -r NUM1=5
NUM2 =4 # Line 4
num3=$((NUM1 + NUM2))
num4=$((NUM1 - NUM2))
num5=$((NUM1 * NUM2))
num6=$((NUM1 / NUM2)) # Line 9
echo "$num3"
echo $((5**2))
echo $((5%4))
I am using this bash script, and when I was running the script, I got the error
./bash_help
./bash_help: line 4: NUM2: command not found
./bash_help: line 9: NUM1 / NUM2: division by 0 (error token is "NUM2")
5
25
1
So I have changed the code to this and the error was gone.
#!/bin/bash
declare -r NUM1=5
NUM2=4
num3=$((NUM1 + NUM2))
num4=$((NUM1 - NUM2))
num5=$((NUM1 * NUM2))
num6=$((NUM1 / NUM2))
echo "$num3"
echo $((5**2))
echo $((5%4))
Why can't we use spaces when we assign a value to a variable? It is convention to use spaces for better readability of the code. Can anyone explain this?
It's not a convention in bash (or, more generally, POSIX-family shells).
As for "why", that's because the various ways of doing it wrong all have valid meanings as commands. If you made NUM2 = 4 an assignment, then you couldn't pass = as a literal argument without quoting it. Consequently, any such change would be backwards-incompatible, rather than being placed in undefined space (where extensions to the POSIX sh standard need to live to avoid constituting violations of that standard).
NUM2= 4 # runs "4" as a command, with the environment variable NUM2 set to an empty string
NUM2 =4 # runs "NUM2" as a command, with "=4" as its argument
NUM2 = 4 # runs "NUM2" as a command, with "=" as its first argument, and "4" as another
In Bash, functions are passed arguments as whitespace separated words.
From the documentation
"Each operator and operand must be a separate argument."
Variable assignment is different and uses this syntax name=[value]
The reason you can't put unquoted spaces around the equals sign is because bash would interpret this as a command.
The reason is, quite simply, that the shell is built to behave like this. It may not make sense for someone with experience in other programming languages (if you call shell syntax a "language", which in a sense it is).
Shell scripting makes it possible in many cases to simply not quote strings (as long as a sequence of characters meant to be a single string does not contain any spacing or special characters). Thanks to this, you can write :
my_command -n -X arg1 arg2
Instead of (in some kind of imaginary pseudo code)
"my_command" "-n" "-X" "arg1" "arg2"
In most languages, it is the other way around : literal strings are quoted, which frees "syntax space" for using variables without any special character (like $ in shell scripting).
Shell syntax provides convenience in frequent cases, at the cost of, well, less convenience (and readability) when doing some other things. It is both a curse and a blessing. The nice thing is knowing that if you have an interactive shell, you can be 100% sure you have an interpreter that will handle some kind of (maybe inelegant) programs. Due to its universal availability (despite various flavors being in existence), the shell is a kind of platform that is useful enough to be worth learning.
I would like to achieve this in Bash: echo $(a=1)and print the value of variable a
I test eval, $$a,{}, $() but none of them work as most of the times either I got literally a=1 or in one case (I don't remember which) it tried to execute the value.
I known that I can do: a=1;echo $a but because I'm little fun one command per line (even if sometimes is getting little complicated) I was wondering if is possible to do this either with echo or with printf
If you know that $a is previously unset, you can do this using the following syntax:
echo ${a:=1}
This, and other types of parameter expansion, are defined in the POSIX shell command language specification.
If you want to assign a numeric value, another option, which doesn't depend on the value previously being unset, would be to use an arithmetic expansion:
echo $(( a = 1 ))
This assigns the value and echoes the number that has been assigned.
It's worth mentioning that what you're trying to do cannot be done in a subshell by design because a child process cannot modify the environment of its parent.
I am trying to get the value of a variable to be selected by name at runtime, using eval, but I don't get its value if - (hyphen) is in the name.
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=\${${DBUSERNAME}}"
eval "PASSWORD=\${${DBPASSWORD}}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
RESULT
echo USERNAME=east-1
echo PASSWORD=east-1
EXPECTED RESULT
echo USERNAME=DB_USER_dev_us-east-1
echo PASSWORD=DB_USER_dev_us-east-1
It's working fine if there is no hyphen present in the name.
Investigation
We can see what's happening by running this in shell with -x option to trace execution:
$ sh -x ./36332134.sh
+ ENV=dev
+ REGION=us-east-1
+ DBUSERNAME=DB_USER_dev_us-east-1
+ DBPASSWORD=DB_PASS_dev_us-east-1
+ eval USERNAME=${DB_USER_dev_us-east-1}
+ USERNAME=east-1
+ eval PASSWORD=${DB_PASS_dev_us-east-1}
+ PASSWORD=east-1
+ echo USERNAME=east-1
USERNAME=east-1
+ echo PASSWORD=east-1
PASSWORD=east-1
Notice that eval USERNAME=${DB_USER_dev_us-east-1} gives us USERNAME=east-1. That's parameter expansion in effect, as described in the Bash manual:
When not performing substring expansion, using the forms documented below (e.g., :-), bash tests for a parameter that is unset
or null. Omitting the colon results in a test only for a parameter
that is unset.
${parameter:-word}
Use Default Values. If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter
is substituted.
Since $DB_USER_dev_us is unset, then the expansion of ${DB_USER_dev_us-east-1} is east-1.
Workarounds
Shell doesn't allow - in variable names (including environment variables). I guess DB_USER_dev_us-east-1 was set by some non-shell program? In which case, you'll need a similar non-shell program to retrieve it, I think. I tested quoting the -, but to no avail.
If you can use Bash as your shell, you might want to use an associative array instead of composing variable names.
If you are able to change the environment variables, you might consider changing the - to (say) _, then using (Bash) ${REGION//-/_} or (otherwise) tr to transform the name:
REGION="${REGION//-/_}" # Bash
REGION="$(echo "$REGION"|tr - _)" # POSIX
You are evaluating/expanding a few too many times.
Toby's answer is exactly correct (and shows the proper debugging technique for this sort of issue) but the solution to the problem is to unwrap one level of expansion.
You wrote
eval "USERNAME=\${${DBUSERNAME}}"
which becomes
eval "USERNAME=\${DB_USER_dev_us-east-1}"
which then gets run through eval as
USERNAME=${DB_USER_dev_us-east-1}
which becomes
USERNAME=east-1
but you wanted to stop after the first expansion. That is
eval "USERNAME=\${DB_USER_dev_us-east-1}"
has already performed the expansion you wanted and gotten you the result you needed. So you don't want the \${...} bit or eval. Just
USERNAME=DB_USER_dev_us-east-1
which you get from
USERNAME=${DBUSERNAME}
Unless I've missed something or your example isn't accurate.
As they have explained the problem, here is what you can do
$> more a.sh
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=${DBUSERNAME}"
eval "PASSWORD=${DBPASSWORD}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
Results
$> ./a.sh
USERNAME=DB_USER_dev_us-east-1
PASSWORD=DB_PASS_dev_us-east-1
How Can I access bash variables in tcl(expect) script.
I have bash file say f1.sh which set some variables like
export var1=a1
export var2=a2
These variable I need to use in my expect script .
I tried using this in my script which does not work
system "./f1.sh"
puts "var1 is $::env(var1)"
puts "var2 is $::env(var2)"
But this does not seems to work.
I see that non of the variable from f1.sh are getting set as environment variable.
system "./f1.sh" << # Is this command in my script right ?
How I need to access these bash variables from tcl file.
I would say that this problem is rather general. First I met this problem, when I wanted to initialize Microsoft Visual Studio environment (which is done using .cmd script) in PoserShell. Later I've faced this problem with other scripting languages in any combinations (Bash, Tcl, Python etc.).
Solution provided by Hai Vu is good. It works well, if you know from the beginning, which variables you need. However, if you are going to use script for initialization of some environment it my contains dozens of variables (which you don't even need to know about, but which are needed for normal operation of the environment).
In general, the solution for the problem is following:
Execute script and at the end print ALL environment variables and capture the output.
Match lines of output for the pattern like "variable=value", where is what you want to get.
Set environment variables using facilities of your language.
I do not have ready made solution, but I guess, that something similar to this should work (note, that snippets below was not tested - they are aimed only to give an idea of the solution):
Execute script; print vars and capture the output (argument expanding - {*} - requires Tcl 8.5, here we can go without it, but I prefer to use it):
set bashCommand {bash -c 'myScriptName arg1 arg2 2>&1 >/dev/null && export -p'}
if [catch {*}${bashCommand} output] {
set errMsg "ERROR: Failed to run script."
append errMsg "\n" $output
error $errMsg
}
;# If we get here, output contains the output of "export -p" command
Parse the output of the command:
set vars [dict create]
foreach line [split $output "\n"] {
regex -- {^declare -x ([[:alpha:]_]*)=\"(.*)\"$} $line dummy var val
;# 3. Store var-val pair of set env var.
}
Store var-val pair or set env var. Here several approaches can be used:
3.1. Set Tcl variables and use them like this (depending on context):
set $var $val
or
variable $var $val
3.2. Set environment variable (actually, sub-case of 3.1):
global ::env
set ::env($var) $val
3.3 Set dict or array and use it within your application (or script) without modification of global environment:
set myEnv($var) val ;# set array
dict set myEnvDict $var $val ;# set dict
I'd like to repeat, that this is only the idea of the receipt. And more important, that as most of the modern scripting languages support regexes, this receipt can provide bridge between almost arbitrary pair of languages, but not only Bash<->Tcl
You can use a here-document, like this:
#!/bin/bash
process=ssh
expect <<EOF
spawn $process
...
EOF
Exported variables are only passed from a parent process to it's children, not the other way around. The script f1.sh (actually the bash instance that's running the script) gets it's own copies of var1 and var2 and it doesn't matter if it changes them, the changes are lost when it exits. For variable exporting to work, you would need to start the expect script from the bash script.
In f1.sh, printf what you want to return...
printf '%s\n%s\n' "$var1" "$var2"
...and read it with exec in Tcl:
lassign [split [exec ./f1.sh] \n] var1 var2
Perhaps I did not look hard enough, but I don't see any way to do this. When you execute the bash script, you create a different process. What happens in that process does not propagate back to the current process.
We can work-around this issue by doing the following (thanks to potrzebie for the idea):
Duplicate the bash script to a temp script
Append to the temp script some commands at the end to echo a marker, and a list of variables and their values
Execute the temp script and parse the output
The result is a list of alternating names and values. We use this list to set the environment variables for our process.
#!/usr/bin/env tclsh
package require fileutil
# Execute a bash script and extract some environment variables
proc getBashVar {bashScript varsList} {
# Duplicate the bash script to a temp script
set tempScriptName [fileutil::tempfile getBashVar]
file copy -force $bashScript $tempScriptName
# Append a marker to the end of the script. We need this marker to
# identify where in the output to begin extracting the variables.
# After that append the list of specified varibles and their values.
set f [open $tempScriptName a]
set marker "#XXX-MARKER"
puts $f "\necho \\$marker"
foreach var $varsList {
puts $f "echo $var \\\"$$var\\\" "
}
close $f
# Execute the temp script and parse the output
set scriptOutput [exec bash $tempScriptName]
append pattern $marker {\s*(.*)}
regexp $pattern $scriptOutput all vars
# Set the environment
array set ::env $vars
# Finally, delete the temp script to clean up
file delete $tempScriptName
}
# Test
getBashVar f1.sh {var1 var2}
puts "var1 = $::env(var1)"
puts "var2 = $::env(var2)"