I currently have a bash script like this, which successfully calls the program prog:
#!/bin/bash
var1=hello
var2=world
prog <<EOF
$var1
$var2
EOF
Instead of var1 and var2, how would I pass each element within an array (of unknown length, since I am using $#) to prog in the same manner?
Strictly speaking, you would want something like
for line in "$#"; do
echo "$line"
done | prog
It's not a here document, but it has the same effect. Here documents and arrays were developed for two different use cases.
Even more strictly speaking, $# is not an array, although it tries very hard to behave like one. :)
You could loop over each element of the array and echo each value into the program:
vars=('foo' 'foo bar' 'bar')
for var in "${vars[#]}"; do echo $var; done | prog
FAIRNESS UPDATE: #chepner beat me to this answer by a few seconds :)
As far as I know, you cannot pass variables, but you can pass arguments, so here's a fix:
prog $VAR1 $VAR2 <<EOF
And inside prog you could use:
ARR=($#)
to save all the positional parameters to the variable ARR.
Related
I want to be able to do:
Script1.sh
declare -A map=(
['A']=1
['B']=2
['C']=3
['D']=4
)
sh script2.sh ???
Script2.sh
params = ...
echo ${params['A']}
ie, access parameters by keys. I have seen related questions for normal arrays and the answer to them has been to pass the array as:
sh script2.sh "${AR[#]}"
which I believe translates to:
sh script2.sh "${map[0]}" "${map[1]}" "${map[2]}"
But with that, I can only access the elements based on their order.
Is there a clever trick to achieve what I want? perhaps with something that passes on "A=1" "B=2" "C=3" "D=4" instead and have script2.sh parse them? or is there a neater solution?
If you are only calling script2.sh from inside script1.sh, then all you need to do (as #markp-fuso pointed out) is source script2.sh and it will run in the current context with all the data already loaded.
If you really want it to be on the command line, then pass it as key=val and have your code in script2.sh check each of it's args for that format and set them in an associative array.
declare -A map=()
for arg in "$#"
do if [[ "$arg" =~ ^[A-Z]=[0-9]$ ]] # more complex k/v will get ugly
then map[${arg/=?}]=${arg/?=} # as will the assignment w/o eval
fi
done
# And finally, just to see what got loaded -
declare -p map
$: script2.sh C=3 A=1
declare -A map=([A]="1" [C]="3" )
As mentioned above, a more complicated possible set of key names and/or values will require a suitably more complex test as well as assignment logic. Clearly, for anything but the simplest cases, this is going to quickly get problematic.
Even better, set up a full getopts loop, and pass your args with proper indicators. This takes more design and more implementation, but that's what it takes to get more functionality.
Assumptions:
the array is the only item being passed to script2 (this could be relaxed but would likely require adding some option flag processing to script2)
the array name will always be map (could probably make this dynamic but that's for another day)
the array indices and values do not contain any special/control characters (eg, line feeds) otherwise passing the array structure on the command line to script2 gets mucho complicated really quick (there are likely some workarounds for this scenario, too)
Some basic components:
The array named map:
$ declare -A map=(
['A']=1
['B']=2
['C']=3
['D']=4
)
Use typeset to generate a command to (re)produce the contents of array map:
$ typeset -p map
declare -A map=([A]="1" [B]="2" [C]="3" [D]="4" )
From here we can pass the typeset output to script2 and then have script2 evaluate the input, eg:
$ cat script1
echo "in script1"
declare -A map=(
['A']=1
['B']=2
['C']=3
['D']=4
)
./script2 $(typeset -p map)
$ cat script2
echo "in script2"
echo " \$# = $#"
eval "$#"
for i in "${!map[#]}"
do
echo "${i} : ${map[${i}]}"
done
Running script1 generates:
$ ./script1
in script1
in script2
$# = declare -A map=([A]="1" [B]="2" [C]="3" [D]="4" )
A : 1
B : 2
C : 3
D : 4
I know, I know, I know ... eval == evil. I'll have to think about a replacement for eval ... also open to suggestions.
I have a problem in one of my scripts, here it is simplified.
I want to name a variable using another variable in it. My script is:
#! /bin/bash
j=1
SAMPLE${j}_CHIP=5
echo ${SAMPLE${j}_CHIP}
This script echoes:
line 3: SAMPLE1_CHIP=5: command not found
line 4: ${SAMPLE${j}_CHIP}: bad substitution
I'm trying to do that in order to name several samples in a while loop changing the "j" parameter.
Anyone knows how to name a variable like that?
It's possible with eval, but don't use dynamic variable names. Arrays are much, much better.
$ j=1
$ SAMPLES[j]=5
$ echo ${SAMPLES[j]}
5
You can initialize an entire array at once like so:
$ SAMPLES=(5 10 15 20)
And you can append with:
$ SAMPLES+=(25 30)
Indices start at 0.
To read the value of the variable, you may use indirection: ${!var}:
#! /bin/bash
j=1
val=get_5
var=SAMPLE${j}_CHIP
declare "$var"="$val"
echo "${!var}"
The problem is to make the variable get the value.
I used declare above, and the known options are:
declare "$var"="$val"
printf -v "$var" '%s' "$val"
eval $var'=$val'
export "$var=$val"
The most risky option is to use eval. If the contents of var or val may be set by an external user, you have set a way to get code injection. It may seem safe today, but after someone edit the code for some reason, it may get changed to give an attacker a chance to "get in".
Probably the best solution is to avoid all the above.
Associative Array
One alternative is to use Associative Arrays:
#! /bin/bash
j=1
val=get_5
var=SAMPLE${j}_CHIP
declare -A array
array[$var]=$val
echo "${array[$var]}"
Quite less risky and you get a similar named index.
Plain array
But it is clear that the safest solution is to use the simplest of solutions:
#! /bin/bash
j=1
val=get_5
array[j]=$val
echo "${array[j]}"
All done, little risk.
If you really want to use variable variables:
#! /bin/bash
j=1
var="SAMPLE${j}_CHIP"
export ${var}=5
echo "${!var}" # prints 5
However, there are other approaches to solving the parent issue, which are likely less confusing than this approach.
j=1
eval "SAMPLE${j}_CHIP=5"
echo "${SAMPLE1_CHIP}"
Or
j=1
var="SAMPLE${j}_CHIP"
eval "$var=5"
echo "${!var}"
As others said, it's normally not possible. Here is a workaround if you wish. Note that you have to use eval when declaring a nested variable, and ⭗ instead of $ when accessing it (I use ⭗ as a function name, because why not).
#!/bin/bash
function ⭗ {
if [[ ! "$*" = *\{*\}* ]]
then echo $*
else ⭗ $(eval echo $(echo $* | sed -r 's%\{([^\{\}]*)\}%$(echo ${\1})%'))
fi
}
j=1
eval SAMPLE${j}_CHIP=5
echo `⭗ {SAMPLE{j}_CHIP}`
c=CHIP
echo `⭗ {SAMPLE{j}_{c}}`
Sorry if the title isn't very clear. So, I'm trying to echo a variable in bash after using read to get a variable ($A in this example) from a user, like so:
foobar="good"
A=bar
B="\$foo$A"
echo $B
But the output of echoing $B is "$foobar", instead of "good", which is what I want it to be. I've tried using eval $B at the end, but it just told me that "good" isn't a command. eval $(echo $B) didn't work either, it also told me that "good" isn't a command. echo $(eval $B) also told me that "good" isn't a command, and also prints an empty line.
To help clarify why I need to do this: foobar is a variable that contains the string "good", and foobaz contains "bad", and I want to let a user choose which string is echoed by typing either "bar" or "baz" while a program runs read A (in this example I just assigned A as bar to make this question more understandable).
If anyone could please tell me how I can make this work, or offer related tips, I'd appreciate it, thanks.
You're looking for indirect references:
$ foobar="hello"
$ A=bar
$ B="foo$A"
$ echo "$B" "${!B}"
foobar hello
You should take a look at the How can I use variable variables FAQ for more information about those (and associative arrays).
I'm writing a bash script which has to pass a variable to another program:
./program $variable
The problem is, it is absolutely necessary for $variable to be passed as a single parameter, which isn't the case if it contains whitespace.
variable=Hello World
./program $variable
-> program receives two arguments: 'Hello' 'World'
Quoting it doesn't do anything at all (well done, bash devs):
variable=Hello World
./program "$variable"
-> program receives: 'Hello' 'World'
Double quoting it does crazy stuff (well done, bash devs):
variable=Hello World
./program "'$variable'"
-> program receives: "'Hello" "World'"
Is there an easy way to do this? Heck, is there a way to do this at all?
Update: Okay, since the problem doesn't seem to be bash, here's some additional info.
The program I'm passing arguments to is a python script. Without modifying the arguments in any way, I get
print sys.argv
-> ['/usr/bin/program', "'Hello", "World'"]
How can I fix that?
Edit: No, I haven't tried
variable="Hello World"
because I never declare $variable. It's not being declared inside my bash function and I'm not allowed to modify it.
Edit: Okay, I got it to work that way.
local temp="$variable"
./program "$temp"
I'd like to know why it works that way and not any other way, though.
did you try with var="hello world"?
i tried this in my solaris box.
> setenv var "hello world"
> cat temp.sh
#!/bin/sh
echo $1
echo $2
> ./temp.sh "$var"
hello world
>
as you can see the $2 is not printed.$var is considered as only one argument.
When you call your script pass the arguments within quotes.
Example script:
#!/bin/bash
for arg in "$#"; do
echo "arg: $1";
shift;
done
When you call it with:
./program "parameter with multiple words" parameter2 parameter3 "another parameter"
The output should be:
arg: parameter with multiple words
arg: parameter2
arg: parameter3
arg: another parameter
Have a look on http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html .
The problem is that the expansion of variables is done before of the command line parameters hence your behavior.
You might work it arround with setting IFS to something weird as
IFS='###' V='foo bar baz'; ./program $V
The problem seems to be inside the "program"
variable="Hello World" # quotes are needed because of the space
./program "$variable" # here quotes again
and inside the program
echo "program received $# arguments:"
i=1
for arg in "$#" # again quotes needed
do echo "arg $((i=i+1)): '$arg'" # and again
done
This is almost certainly a problem in the way you are reading the variable in your program.
For instance suppose this is your script (just one line for testing):
echo "$1"
Let's call it echo.sh. If you run echo.sh "test best", you will get test best.
But if your program says
echo $1
you might get behaviour like what you're seeing.
I want to retrieve the n-th parameter of $# (the list of command line parameters passed to the script), where n is stored in a variable.
I tried ${$n}.
For example, I want to get the 2nd command line parameter of an invocation:
./my_script.sh alpha beta gamma
And the index should not be explicit but stored in a variable n.
Sourcecode:
n=2
echo ${$n}
I would expect the output to be "beta", but I get the error:
./my_script.sh: line 2: ${$n}: bad substitution
What am I doing wrong?
You can use variable indirection. It is independent of arrays, and works fine in your example:
n=2
echo "${!n}"
Edit: Variable Indirection can be used in a lot of situations. If there is a variable foobar, then the following two variable expansions produce the same result:
$foobar
name=foobar
${!name}
Try this:
#!/bin/bash
args=("$#")
echo ${args[1]}
okay replace the "1" with some $n or something...
The following works too:
#!/bin/bash
n=2
echo ${#:$n:1}
The portable (non-bash specific) solution is
$ set a b c d
$ n=2
$ eval echo \${$n}
b
eval can help you access the variable indirectly, which means evaluate the expression twice.
You can do like this eval alph=\$$n; echo $alph