How to assign the # array to another variable in bash? - arrays

Amongst the answers to How to copy an array in Bash?, the solution for copying an array from one variable to another is arrayClone=("${oldArray[#]}").
However, what if the array I need to copy is the list of arguments, #?
A simple test script like:
#! /bin/bash
argsCopy=("${#[#]}")
Fails with an error:
line 3: ${#[#]}: bad substitution

By way of experimentation, it appears that argsCopy=("$#") is sufficient.
When I run the following via ./test.sh 1 2 3\ 4,
#! /bin/bash
set -x
argsCopy=("$#")
echo "${argsCopy[#]}" > /dev/null
it outputs:
+ argsCopy=("$#")
+ echo 1 2 '3 4'
However, like many things in sh/bash, I can't explain what rules of the language cause this to work, or under what circumstances it might end up failing.

Related

Why does "echo $array" print all members of the array in this specific case instead of only the first member like in any other case?

I have encountered a very curious problem, while trying to learn bash.
Usually trying to print an echo by simply parsing the variable name like this only outputs the first member Hello.
#!/bin/bash
declare -a test
test[0]="Hello"
test[1]="World"
echo $test # Only prints "Hello"
BUT, for some reason this piece of code prints out ALL members of the given array.
#!/bin/bash
declare -a files
counter=0
for file in "./*"
do
files[$counter]=$file
let $((counter++))
done
echo $files # prints "./file1 ./file2 ./file3" and so on
And I can't seem to wrap my head around it on why it outputs the whole array instead of only the first member. I think it has something to do with my usage of the foreach-loop, but I was unable to find any concrete answer. It's driving me crazy!
Please send help!
When you quoted the pattern, you only created a single entry in your array:
$ declare -p files
declare -a files=([0]="./*")
If you had quoted the parameter expansion, you would see
$ echo "$files"
./*
Without the quotes, the expansion is subject to pathname generation, so echo receives multiple arguments, each of which is printed.
To build the array you expected, drop the quotes around the pattern. The results of pathname generation are not subject to further word-splitting (or recursive pathname generation), so no quotes would be needed.
for file in ./*
do
...
done

Using array from an output file in bash shell

am in a need of using an array to set the variable value for further manipulations from the output file.
scenario:
> 1. fetch the list from database
> 2. trim the column using sed to a file named x.txt (got specific value as that is required)
> 3. this file x.txt has the below output as
10000
20000
30000
> 4. I need to set a variable and assign the above values to it.
A=10000
B=20000
C=30000
> 5. I can invoke this variable A,B,C for further manipulations.
Please let me know how to define an array assigning to its variable from the output file.
Thanks.
In bash (starting from version 4.x) and you can use the mapfile command:
mapfile -t myArray < file.txt
see https://stackoverflow.com/a/30988704/10622916
or another answer for older bash versions: https://stackoverflow.com/a/46225812/10622916
I am not a big proponent of using arrays in bash (if your code is complex enough to need an array, it's complex enough to need a more robust language), but you can do:
$ unset a
$ unset i
$ declare -a a
$ while read line; do a[$((i++))]="$line"; done < x.txt
(I've left the interactive prompt in place. Remove the leading $
if you want to put this in a script.)

How can for i in ${VAR} ever work in bash, instead of for i in "${VAR[#]}"?

If I try on my machine (with bash 3,4 and 5) the following command:
bash-5.0$ VAR=(1 2 3)
bash-5.0$ for i in ${VAR}; do echo $i; done
I get only one line with the 1.
If I do the same on ZSH for example, it nicely writes the three lines with progressive numbers.
However in one of our production servers I found this:
bash -c "for i in ${MY_VAR}; do stuff with $i; done"
And by checking the logs it seems that it is actually iterating correctly!
How is this possible? Is it a particular version of bash I’m not aware of? Or some flag I should set? Or maybe the array was populated in a particular way?
It "works" because the code isn't actually using an array at all.
export MY_VAR='1 2 3'
bash -c 'for i in ${MY_VAR}; do echo "Doing stuff with $i"; done'
...involves no arrays whatsoever; MY_VAR is a string being word-split and then glob-expanded.
Don't do that, ever, even if you really do need to iterate over items from a delimiter-separated string. The reliable alternative is to use read -r -a my_array <<<"$MY_VAR" to read your string into an array, and then for i in "${my_array[#]}"; do echo "Doing stuff with $i"; done to iterate over it.
You should write:
var=(1 2 3)
for i in "${var[#]}"; do
do stuff with "$i"
done
You need [#] as shown. And don't use uppercase variable names. Now as to why it works on your production server: possibly because MY_VAR is defined as MY_VAR="1 2 3" (or something analogous), i.e., MY_VAR isn't an array (which is bad).
Looks like Bash evaluates $arr to ${arr[0]}:
arr=(1 2 3)
echo $arr # yields 1
arr[0]=999
echo $arr # yields 999
With associative arrays:
declare -A h
h=([one]=1 [two]=2)
echo $h # yields nothing
h=([0]=1 [two]=2)
echo $h # yields 1
As others have pointed out, the right way to loop through an array is:
for i in "${arr[#]}"; do ...

Giving inputs to an executed program automatically

I have a C program that I want to run without having to manually type commands into. I have 4 commands (5 if you count the one to exit the program) that I want given to the program and I don't know where to start. I have seen some stuff like
./a.out <<<'name'
to pass in a single string but that doesn't quite work for me.
Other issues I have that make this more difficult are that one of the commands will give an output and that output needs to be a part of a later command. If I had access to the source code I could just brute force in some loops and counters so I am trying to get a hold of it but for now I am stuck working without it. I was thinking there was a way to do this with bash scripts but I don't know what that would be.
In simple cases, bash script is a possibility: run the executable in coproc (requires version 4). A short example:
#!/bin/bash
coproc ./parrot
echo aaa >&${COPROC[1]}
read result <&${COPROC[0]}
echo $result
echo exit >&${COPROC[1]}
with parrot (a test executable):
#!/bin/bash
while [ true ]; do
read var
if [ "$var" = "exit" ]; then exit 0; fi
echo $var
done
For a more serious scenarios, use expect.

translation from bash to ash shell: how to handle arrays defined by input?

I try to transfer the excellent example docker-haproxy from centos to alpine.
A shell script is used to process a list of values given as parameters to the script into an array, then write these values plus their index to some file.
The following construction works in bash:
ServerArray=${SERVERS:=$1}
...
for i in ${ServerArray[#]}
do
echo " " server SERVER_$COUNT $i >> /haproxy/haproxy.cfg
let "COUNT += 1"
done
but not in ash (or sh):
syntax error: bad substitution
The error refers to line
for i in ${ServerArray[#]}
What is the correct syntax here? I guess the line
ServerArray=${SERVERS:=$1}
does not define an array as intended, but googling for long did not help me.
bash to sh (ash) spoofing says
sh apparently has no arrays.
If so, how to solve the problem then?
I guess I can do with this construction:
#!/bin/sh
# test.sh
while [ $# -gt 0 ]
do
echo $1
shift
done
delivers
/ # ./test 172.17.0.2:3306 172.17.0.3:3306
172.17.0.2:3306
172.17.0.3:3306
which is what I need to proceed

Resources