Loop inside "heredoc" in shell scripting - bash

I need to execute series of commands inside an interactive program/utility with parameterized values. Is there a way to loop inside heredoc ? Like below .. Not sure if eval can be of any help here. Below example doesn't seem to work as the interactive doesn't seem to recognize system commands.
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
utilityExecutable << EOF
for i in $list ; do
utilityCommand $i
done
EOF

Instead of passing a here-document to utilityExecutable,
the equivalent is to pipe the required text to it. You can create the desired text using echo statements in a for-loop, and pipe the entire loop output to utilityExecutable:
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
for i in $list; do
echo "utilityCommand $i"
done | utilityExecutable

Yes, this is tricky and can be confusing! You have to modify your codes as follow.
#!/bin/sh
list="OBJECT1 OBJECT2 OBJECT3"
utilityExecutable << EOF
list="$list"
for i in \$list ; do
utilityCommand \$i
done
EOF
This is because heredoc uses its own variables, which are completely separate from the shell. When you are inside heredoc, you have to use and modify heredoc's own variables. So the \$ is needed to reference heredoc's own variables instead of shell variables when inside heredoc.

cat << EOF
$(
for i in {1..10}; do
echo $i;
done
)
EOF

commandxyz -noenv<<EOF
echo "INFO - Inside eof"
t_files=("${p_files[#]}")
#copy array
#echo \${t_files[*]}
#all elements from array
#echo \${#t_files[#]}
#array length
for i in \${t_files[#]} ; do
echo -e \$i;
do other stuff \$i;
done
cat $patch_file
git apply $patch_file
EOF

myVar=$(
for i in {1..5}; do
echo hello;
echo world;
done;
); cat <<< $myVar

Related

Create a bash script inside a bash script that uses special variables $1, $#

I'm trying to create a script that creates an other script that uses $1 and $#, the problem is that those variables are being interpreted by the first script, so they are empty. Here's my problem, the first script creates the script /tmp/test.sh
#!/bin/bash
cat << EOF > /tmp/test.sh
#!/bin/bash
echo $1
echo $#
EOF
The result in /tmp/test.sh:
#!/bin/bash
echo
echo 0
Does anyone know how to avoid this and get in /tmp/test.sh $1 and $#?
I would like to have in /tmp/test.sh:
#!/bin/bash
echo $1
echo $#
Thanks in advance.
Quote the here-document delimiter so that the contents of the here document are treated as literal text (i.e., as if occurring in a single-quoted string).
cat << 'EOF' > /tmp/test.sh
#!/bin/bash
echo $1
echo $#
EOF
Any quoting will work, not just single quotes. The only important thing is that at least one character be escaped.
cat << \EOF
cat << "EOF"
cat << E"O"F
etc

Writing a Nested shell script- Unable to pass argument

I am trying to create a shell script using another script.
Following is the code.
#!/bin/bash
count=$#
cat << EOF > /tmp/kill_loop.sh
#!/bin/bash
while true;
do
for i in "$#"
do
echo $i
done
done
EOF
When I see kill_loop.sh , "$i" is empty.
#!/bin/bash
while true;
do
for i in "one two three"
do
echo
done
done
I want "$i" to be printed as such in kill_loop.sh file so that if i execute kill_loop.sh, it echoes the value 'one','two' and 'three'
Your "outer" shell script is interpreting $i as if it were one of its own variables, which isn't set, thus it evaluates to nothing. Try escaping the $ so the outer shell doesn't expand it:
echo \$i
here is the "foreach" function:
function foreach
{
typeset cmd=$1;
shift;
for arg in "$#";
do
$cmd $arg;
done
}

Set a parent shell's variable from a subshell

How do I set a variable in the parent shell, from a subshell?
a=3
(a=4)
echo $a
The whole point of a subshell is that it doesn't affect the calling session. In bash a subshell is a child process, other shells differ but even then a variable setting in a subshell does not affect the caller. By definition.
Do you need a subshell? If you just need a group then use braces:
a=3
{ a=4;}
echo $a
gives 4 (be careful of the spaces in that one). Alternatively, write the variable value to stdout and capture it in the caller:
a=3
a=$(a=4;echo $a)
echo $a
avoid using back-ticks ``, they are deprecated and can be difficult to read.
There is the gdb-bash-variable hack:
gdb --batch-silent -ex "attach $$" -ex 'set bind_variable("a", "4", 0)';
although that always sets a variable in the global scope, not just the parent scope
You don't. The subshell doesn't have access to its parent's environment. (At least within the abstraction that Bash provides. You could potentially try to use gdb, or smash the stack, or whatnot, to gain such access clandestinely. I wouldn't recommend that, though.)
One alternative is for the subshell to write assignment statements to a temporary file for its parent to read:
a=3
(echo 'a=4' > tmp)
. tmp
rm tmp
echo "$a"
If the problem is related to a while loop, one way to fix this is by using Process Substitution:
var=0
while read i;
do
# perform computations on $i
((var++))
done < <(find . -type f -name "*.bin" -maxdepth 1)
as shown here: https://stackoverflow.com/a/13727116/2547445
To change variables in a script called from a parent script, you can call the script preceded with a "."
(EDIT - for explanation)
In most shells "." is an alias for "source". the source command just inserts the text of another file at that position in the executing script. In the context of this question this answer avoids a sub-shell
a=3
echo $a
. ./calledScript.sh
echo $a
in calledScript.sh
a=4
Expected output
3
4
By reading the answer from #ruakh (thank you) with a temporary file approach and the comments asking for a file descriptors solution, I got the following idea:
a=3
. <(echo a=4; echo b=5)
echo $a
echo $b
It allows returning different variables at once (which could be an issue in the subshell variant of the accepted answer).
No iteration is needed,
No temporary file to take care of.
Close to the syntax proposed by the OP.
Result:
4
5
With xtrace enabled is visible that we are sourcing from the file descriptor created for the output of the subshell:
+ a=3
+ . /dev/fd/63 # <-- the file descriptor ;)
++ echo a=4
++ echo b=5
++ a=4
++ b=5
+ echo 4
4
+ echo 5
5
You can output the value in the subshell and assign the subshell output to a variable in the caller script:
# subshell.sh
echo Value
# caller
myvar=$(subshell.sh)
If the subshell has more to output you can separate the variable value and other messages by redirecting them into different output streams:
# subshell.sh
echo "Writing value" 1>&2
echo Value
# caller
myvar=$(subshell.sh 2>/dev/null) # or to somewhere else
echo $myvar
Alternatively, you can output variable assignments in the subshell, evaluate them in the caller script and avoid using files to exchange information:
# subshell.sh
echo "a=4"
# caller
# export $(subshell.sh) would be more secure, since export accepts name=value only.
eval $(subshell.sh)
echo $a
The last way I can think of is to use exit codes but this covers the integer values exchange only (and in a limited range) and breaks the convention for interpreting exit codes (0 for success non-0 for everything else).
Instead of accessing the variable from the parent shell, change the order of the commands and use the process substitution:
a=3
echo 5 | (read a)
echo $a
prints 3
a=3
read a < <(echo 5)
echo $a
prints 5
Another example:
let i=0
seq $RANDOM | while read r
do
let i=r
done
echo $i
vs
let i=0
while read r
do
let i=r
done < <(seq $RANDOM)
echo $i
Alternatively, when job control is inactive (e.g. in scripts) you can use the lastpipe shell option to achieve the same result without changing the order of the commands:
#!/bin/bash
shopt -s lastpipe
let i=0
seq $RANDOM | while read r
do
let i=r
done
echo $i
Unless you can apply all io to pipes and use file handles, basic variable updating is impossible within $(command) and any other sub-process.
Regular files, however, are bash's global variables for normal sequential processing. Note: Due to race conditions, this simple approach is not good for parallel processing.
Create an set/get/default function like this:
globalVariable() { # NEW-VALUE
# set/get/default globalVariable
if [ 0 = "$#" ]; then
# new value not given -- echo the value
[ -e "$aRam/globalVariable" ] \
&& cat "$aRam/globalVariable" \
|| printf "default-value-here"
else
# new value given -- set the value
printf "%s" "$1" > "$aRam/globalVariable"
fi
}
"$aRam" is the directory where values are stored. I like it to be a ram disk for speed and volatility:
aRam="$(mktemp -td $(basename "$0").XXX)" # temporary directory
mount -t tmpfs ramdisk "$aRam" # mount the ram disk there
trap "umount "$aRam" && rm -rf "$aRam"" EXIT # auto-eject
To read the value:
v="$(globalVariable)" # or part of any command
To set the value:
globalVariable newValue # newValue will be written to file
To unset the value:
rm -f "$aRam/globalVariable"
The only real reason for the access function is to apply a default value because cat will error given a non-existent file. It is also useful to apply other get/set logic. Otherwise, it would not be needed at all.
An ugly read method avoiding cat's non-existent file error:
v="$(cat "$aRam/globalVariable 2>/dev/null")"
A cool feature of this mess is that you can open another terminal and examine the contents of the files while the program is running.
While it's harder to get multiple variables out of a subshell, you can set multiple variables inside a function without using globals.
You can pass the name of a variable into a function that uses local -n to turn it into a special variable called a nameref:
myfunc() {
local -n OUT=$1
local -n SIDEEFFECT=$2
OUT='foo'
SIDEEFFECT='bar'
}
myfunc A B
echo $A
> foo
echo $B
> bar
This is the technique I ended up using instead of getting subshell FOO=$(myfunc) working setting multiple variables.
A very simple and practical method that allows multiple variables is as follows, eventually may add parameters to the call:
function ComplexReturn(){
# do your processing...
a=123
b=456
echo -n "AAA=${a}; BBB=${b};"
}
# ... this can be internal function or any subshell command
eval $(ComplexReturn)
echo $AAA $BBB

how do i create a variable from the output of another in bash

Here's an example script that doesn't work the way I expect:
#!/bin/bash
for dynamic in a b c; do
myvar=$dynamic
export $myvar="hi"
echo $(eval "$myvar")
echo $dynamic
done
I want the output would be:
hi
a
hi
b
hi
c
Any ideas? I'm willing to stray away from this method, but I definitely want to be able to create a variable named from the output of an algorithm. In this case it's just a for loop.
eval has a tendency to cause bugs, so avoid it whenever possible; in this case it's much cleaner to use indirect expansion with ${!metavariable}:
#!/bin/bash
for dynamic in a b c; do
myvar=$dynamic
export $myvar="hi"
echo ${!myvar}
echo $dynamic
done
The following is the fix for your program. There are two things you got wrong:
The first is you don't need '$' when declaring variables.
The second is that calling eval will treat the content of myvar as a shell script. However you don't have "hi" defined anywhere as a command.
for dynamic in a b c; do
myvar=$dynamic
- export $myvar="hi"
+ export myvar="hi"
- echo $(eval "$myvar")
+ echo "$myvar"
echo $dynamic
done
It's not entirely clear that this is what you're looking for, but I think you want something like:
#!/bin/sh
a=A
b=B
c=C
for i in a b c; do
eval $i=value_$i
eval echo \$$i
done
echo $a # Prints "value_a"

Indirect parameter substitution in shell script

I'm having a problem with a shell script (POSIX shell under HP-UX, FWIW). I have a function called print_arg into which I'm passing the name of a parameter as $1. Given the name of the parameter, I then want to print the name and the value of that parameter. However, I keep getting an error. Here's an example of what I'm trying to do:
#!/usr/bin/sh
function print_arg
{
# $1 holds the name of the argument to be shown
arg=$1
# The following line errors off with
# ./test_print.sh[9]: argval=${"$arg"}: The specified substitution is not valid for this command.
argval=${"$arg"}
if [[ $argval != '' ]] ; then
printf "ftp_func: $arg='$argval'\n"
fi
}
COMMAND="XYZ"
print_arg "COMMAND"
I've tried re-writing the offending line every way I can think of. I've consulted the local oracles. I've checked the online "BASH Scripting Guide". And I sharpened up the ol' wavy-bladed knife and scrubbed the altar until it gleamed, but then I discovered that our local supply of virgins has been cut down to, like, nothin'. Drat!
Any advice regarding how to get the value of a parameter whose name is passed into a function as a parameter will be received appreciatively.
You could use eval, though using direct indirection as suggested by SiegeX is probably nicer if you can use bash.
#!/bin/sh
foo=bar
print_arg () {
arg=$1
eval argval=\"\$$arg\"
echo "$argval"
}
print_arg foo
In bash (but not in other sh implementations), indirection is done by: ${!arg}
Input
foo=bar
bar=baz
echo $foo
echo ${!foo}
Output
bar
baz
This worked surprisingly well:
#!/bin/sh
foo=bar
print_arg () {
local line name value
set | \
while read line; do
name=${line%=*} value=${line#*=\'}
if [ "$name" = "$1" ]; then
echo ${value%\'}
fi
done
}
print_arg foo
It has all the POSIX clunkiness, in Bash would be much sorter, but then again, you won't need it because you have ${!}. This -in case it proves solid- would have the advantage of using only builtins and no eval. If I were to construct this function using an external command, it would have to be sed. Would obviate the need for the read loop and the substitutions. Mind that asking for indirections in POSIX without eval, has to be paid with clunkiness! So don't beat me!
Even though the answer's already accepted, here's another method for those who need to preserve newlines and special characters like Escape ( \033 ): Storing the variable in base64.
You need: bc, wc, echo, tail, tr, uuencode, uudecode
Example
#!/bin/sh
#====== Definition =======#
varA="a
b
c"
# uuencode the variable
varB="`echo "$varA" | uuencode -m -`"
# Skip the first line of the uuencode output.
varB="`NUM=\`(echo "$varB"|wc -l|tr -d "\n"; echo -1)|bc \`; echo "$varB" | tail -n $NUM)`"
#====== Access =======#
namevar1=varB
namevar2=varA
echo simple eval:
eval "echo \$$namevar2"
echo simple echo:
echo $varB
echo precise echo:
echo "$varB"
echo echo of base64
eval "echo \$$namevar1"
echo echo of base64 - with updated newlines
eval "echo \$$namevar1 | tr ' ' '\n'"
echo echo of un-based, using sh instead of eval (but could be made with eval, too)
export $namevar1
sh -c "(echo 'begin-base64 644 -'; echo \$$namevar1 | tr ' ' '\n' )|uudecode"
Result
simple eval:
a b c
simple echo:
YQpiCmMK ====
precise echo:
YQpiCmMK
====
echo of base64
YQpiCmMK ====
echo of base64 - with updated newlines
YQpiCmMK
====
echo of un-based, using sh instead of eval (but could be made with eval, too)
a
b
c
Alternative
You also could use the set command and parse it's output; with that, you don't need to treat the variable in a special way before it's accessed.
A safer solution with eval:
v=1
valid_var_name='[[:alpha:]_][[:alnum:]_]*$'
print_arg() {
local arg=$1
if ! expr "$arg" : "$valid_var_name" >/dev/null; then
echo "$0: invalid variable name ($arg)" >&2
exit 1
fi
local argval
eval argval=\$$arg
echo "$argval"
}
print_arg v
print_arg 'v; echo test'
Inspired by the following answer.

Resources