Variables and the CLI? - bash

I want the following:
$ DOMAIN=chron echo snippet-www.$DOMAIN.com-head.html
to output:
snippet.www.chron.com-head.html
but for the life of me, I can't figure out how do this except via the two commands:
$ export DOMAIN=chron
$ echo snippet-www.$DOMAIN.com-head.html
Isn't there a way to get this to work as one command?

I have stumbled upon the answer!!
Just add a semicolon (;) after the variable assignment.
$ DOMAIN=chron; echo snippet-www.$DOMAIN.com-head.html
snippet-www.chron.com-head.html

$ ( export DOMAIN=chron ; echo snippet-www.$DOMAIN.com-head.html )
This makes $DOMAIN an environment variable (which doesn't matter for this example, but might for other similar commands), and it limits its lifetime to the parentheses.
Your answer:
$ DOMAIN=chron; echo snippet-www.$DOMAIN.com-head.html
causes $DOMAIN to be a (non-exported) shell variable, and retains the setting for later commands.

this should work
DOMAIN=chron eval 'echo snippet-www.$DOMAIN.com-head.html'

Related

How can I save environment variables in a file using BASH? [duplicate]

I have two shell scripts that I'd like to invoke from a C program. I would like shell variables set in the first script to be visible in the second. Here's what it would look like:
a.sh:
var=blah
<save vars>
b.sh:
<restore vars>
echo $var
The best I've come up with so far is a variant on "set > /tmp/vars" to save the variables and "eval $(cat /tmp/vars)" to restore them. The "eval" chokes when it tries to restore a read-only variable, so I need to grep those out. A list of these variables is available via "declare -r". But there are some vars which don't show up in this list, yet still can't be set in eval, e.g. BASH_ARGC. So I need to grep those out, too.
At this point, my solution feels very brittle and error-prone, and I'm not sure how portable it is. Is there a better way to do this?
One way to avoid setting problematic variables is by storing only those which have changed during the execution of each script. For example,
a.sh:
set > /tmp/pre
foo=bar
set > /tmp/post
grep -v -F -f/tmp/pre /tmp/post > /tmp/vars
b.sh:
eval $(cat /tmp/vars)
echo $foo
/tmp/vars contains this:
PIPESTATUS=([0]="0")
_=
foo=bar
Evidently evaling the first two lines has no adverse effect.
If you can use a common prefix on your variable names, here is one way to do it:
# save the variables
yourprefix_width=1200
yourprefix_height=2150
yourprefix_length=1975
yourprefix_material=gravel
yourprefix_customer_array=("Acme Plumbing" "123 Main" "Anytown")
declare -p $(echo ${!yourprefix#}) > varfile
# load the variables
while read -r line
do
if [[ $line == declare\ * ]]
then
eval "$line"
fi
done < varfile
Of course, your prefix will be shorter. You could do further validation upon loading the variables to make sure that the variable names conform to your naming scheme.
The advantage of using declare is that it is more secure than just using eval by itself.
If you need to, you can filter out variables that are marked as readonly or select variables that are marked for export.
Other commands of interest (some may vary by Bash version):
export - without arguments, lists all exported variables using a declare format
declare -px - same as the previous command
declare -pr - lists readonly variables
If it's possible for a.sh to call b.sh, it will carry over if they're exported. Or having a parent set all the values necessary and then call both. That's the most secure and sure method I can think of.
Not sure if it's accepted dogma, but:
bash -c 'export foo=bar; env > xxxx'
env `cat xxxx` otherscript.sh
The otherscript will have the env printed to xxxx ...
Update:
Also note:
man execle
On how to set environment variables for another system call from within C, if you need to do that. And:
man getenv
and http://www.crasseux.com/books/ctutorial/Environment-variables.html
An alternative to saving and restoring shell state would be to make the C program and the shell program work in parallel: the C program starts the shell program, which runs a.sh, then notifies the C program (perhaps passing some information it's learned from executing a.sh), and when the C program is ready for more it tells the shell program to run b.sh. The shell program would look like this:
. a.sh
echo "information gleaned from a"
arguments_for_b=$(read -r)
. b.sh
And the general structure of the C program would be:
set up two pairs of pipes, one for C->shell and one for shell->C
fork, exec the shell wrapper
read information gleaned from a on the shell->C pipe
more processing
write arguments for b on the C->shell pipe
wait for child process to end
I went looking for something similar and couldn't find it either, so I made the two scripts below. To start, just say shellstate, then probably at least set -i and set -o emacs which this reset_shellstate doesn't do for you. I don't know a way to ask bash which variables it thinks are special.
~/bin/reset_shellstate:
#!/bin/bash
__="$PWD/shellstate_${1#_}"
trap '
declare -p >"'"$__"'"
trap >>"'"$__"'"
echo cd \""$PWD"\" >>"'"$__"'" # setting PWD did this already, but...
echo set +abefhikmnptuvxBCEHPT >>"'"$__"'"
echo set -$- >>"'"$__"'" # must be last before sed, see $s/s//2 below
sed -ri '\''
$s/s//2
s,^trap --,trap,
/^declare -[^ ]*r/d
/^declare -[^ ]* [A-Za-z0-9_]*[^A-Za-z0-9_=]/d
/^declare -[^ ]* [^= ]*_SESSION_/d
/^declare -[^ ]* BASH[=_]/d
/^declare -[^ ]* (DISPLAY|GROUPS|SHLVL|XAUTHORITY)=/d
/^declare -[^ ]* WINDOW(ID|PATH)=/d
'\'' "'"$__"'"
shopt -op >>"'"$__"'"
shopt -p >>"'"$__"'"
declare -f >>"'"$__"'"
echo "Shell state saved in '"$__"'"
' 0
unset __
~/bin/shellstate:
#!/bin/bash
shellstate=shellstate_${1#_}
test -s $shellstate || reset_shellstate $1
shift
bash --noprofile --init-file shellstate_${1#_} -is "$#"
exit $?

bash not recognizing new lines in multiline variable assignment [duplicate]

In Bash (or other shells) how can I print an environment variable which has a multi-line value?
text='line1
line2'
I know a simple usual echo $text won't work out of the box.
Would some $IFS tweak help?
My current workaround is something like ruby -e 'print ENV["text"]'.
Can this be done in pure shell? I was wondering if env command would take an unresolved var name but it does not seem to.
Same solution as always.
echo "$text"
export TEST="A\nB\nC"
echo $TEST
gives output:
A\nB\nC
but:
echo -e $TEST
A
B
C
So, the answer seems to be the '-e' parameter to echo, assuming that I understand your question correctly.

Can I export variable with another variable inside?

I have a .sh script which uses environment variables I export before. I would like my variables to be like this: VAR1="${libDir}/test" so that in my script, the ${libDir} will be replaced with some value. I declared my export like below: export VAR1="${libDir}/test" but in my script the libDir is not taken into account at all. Can I do it this way?
No, you can't have a variable that contains a variable reference that gets substituted in a "delayed" fashion:
$ libDir=foo
$ VAR1="${libDir}/test"
$ libDir=bar
$ echo "$VAR1"
foo/test
You could get around this using eval, but you shouldn't.
However, a function will do exactly what you want, with the price of a touch extra syntax:
$ var1() {
echo "${libDir}/test"
}
$ libDir=foo
$ echo "$(var1)"
foo/test
$ libDir=bar
$ echo "$(var1)"
bar/test

Print a variable with multi-line value in shell?

In Bash (or other shells) how can I print an environment variable which has a multi-line value?
text='line1
line2'
I know a simple usual echo $text won't work out of the box.
Would some $IFS tweak help?
My current workaround is something like ruby -e 'print ENV["text"]'.
Can this be done in pure shell? I was wondering if env command would take an unresolved var name but it does not seem to.
Same solution as always.
echo "$text"
export TEST="A\nB\nC"
echo $TEST
gives output:
A\nB\nC
but:
echo -e $TEST
A
B
C
So, the answer seems to be the '-e' parameter to echo, assuming that I understand your question correctly.

Expanding variables Bash Scripting

I have two variables in bash that complete the name of another one, and I want to expand it but don't know how to do it
I have:
echo $sala
a
echo $i
10
and I want to expand ${a10} in this form ${$sala$i} but apparently the {} scape the $ signs.
There are a few ways, with different advantages and disadvantages. The safest way is to save the complete parameter name in a single parameter, and then use indirection to expand it:
tmp="$sala$i" # sets $tmp to 'a10'
echo "${!tmp}" # prints the parameter named by $tmp, namely $a10
A slightly simpler way is a command like this:
eval echo \${$sala$i}
which will run eval with the arguments echo and ${a10}, and therefore run echo ${a10}. This way is less safe in general — its behavior depends a bit more chaotically on the values of the parameters — but it doesn't require a temporary variable.
Use the eval.
eval "echo \${$sala$i}"
Put the value in another variable.
result=$(eval "echo \${$sala$i}")
The usual answer is eval:
sala=a
i=10
a10=37
eval echo "\$$sala$i"
This echoes 37. You can use "\${$sala$i}" if you prefer.
Beware of eval, especially if you need to preserve spaces in argument lists. It is vastly powerful, and vastly confusing. It will work with old shells as well as Bash, which may or may not be a merit in your eyes.
You can do it via indirection:
$ a10=blah
$ sala=a
$ i=10
$ ref="${sala}${i}"
$ echo $ref
a10
$ echo ${!ref}
blah
However, if you have indexes like that... an array might be more appropriate:
$ declare -a a
$ i=10
$ a[$i]="test"
$ echo ${a[$i]}
test

Resources