I would like to concatenate unlimited numbers of arrays using shortest lines possible, so for this I did the code below:
#!/bin/bash
declare -a list1=("element1")
declare -a list2=("element2")
declare -a list3=("element3")
declare -a list4=("element4")
declare -a list
for i in {1..4}
do
list=( ${list[#]} ${list$i[#]} )
done
echo ${list[*]}
But the code above is not working because $i is not seen as variable and the error is: ${list$i[#]} bad substitution
You can use variable indirection:
for i in {1..4} ; do
ref="list$i[#]"
list+=("${!ref}")
done
echo "${list[#]}"
The following code outputs all 4 lists concatenated together.
eval echo \${list{1..4}[*]}
This code runs filename expansion over the result of list elements (* is replaced by filenames). Consider sacrificing 4 characters and doing \"\${list{1..4}[*]}\".
Note that eval is evil https://mywiki.wooledge.org/BashFAQ/048 and such code is confusing. I wouldn't write such code in a real script - I would definitely use a loop. Use shellcheck to check your scripts.
Related
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
Problem
I'm writing an bash script (version 4.3.48). There I have an array and want to concatenate all entries as a single string. The following code do such task (but lag in some case):
declare -a array=(one two three)
echo "${array[#]}"
Unfortunately I get this output, including spaces in between the array entries:
one two three
But what I actually need is this:
onetwothree
Background
I try to avoid using a for-loop and concatenate it on my own, cause I call this very often (more than each second) and I guess such a loop is much more expensive than use a build-in function.
So any suggestions how to obtain the required result?
printf gives you a lot more control over formatting, and it is also a bash builtin:
printf %s "${array[#]}" $'\n'
(That works because the shell's printf keeps on repeating the pattern until the arguments are all used up.)
First, that's the wrong way to initialize an array:
$ declare -A array=( one two three )
bash: array: one: must use subscript when assigning associative array
bash: array: two: must use subscript when assigning associative array
bash: array: three: must use subscript when assigning associative array
declare -A is for associative arrays. Just do this
$ array=( one two three )
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three")'
You want to use the * subscript instead of #. That joins the array elements using the first character of the IFS array. If you don't want any separator, assign the empty string to IFS. Since lots of things depend on IFS, I typically use a subshell to contain the modifications:
$ (IFS=; echo "${array[*]}")
onetwothree
To assign the result to a variable, it's the usual command substitution syntax:
$ joined=$(IFS=; echo "${array[*]}"); echo "$joined"
onetwothree
And we can see that the IFS value in this shell (space, tab, newline) is unchanged:
$ printf "%s" "$IFS" | od -c
0000000 \t \n
0000003
If performance is a goal and you want to avoid spawning a subshell, use a function:
$ concat_array() { local -n a=$1; local IFS=; echo "${a[*]}"; }
$ concat_array array
onetwothree
with bash version older than 4.3, use an indirect variable instead of a nameref
$ concat_array() { local tmp="${1}[*]"; local IFS=; echo "${!tmp}"; }
Using for loop
declare -a array=(one two three)
for item in "${array[#]}";do
s+="$item"
done
echo "$s"
I have a little bash script that I had working before. Where I used Unix IFS to import a key/value from a text file:
#!/bin/bash
KEY=/home/myusr/.keyinfo
IFS="
"
set -A arr $(cat $KEY)
echo "This is ${arr[0]}"
echo "This is ${arr[1]}"
Input .keyinfo file:
ABC 123
However, I'm trying to get this to work on a different flavor of Linux and I'm getting this error message:
./tst3.sh: line 7: set: -A: invalid option
set: usage: set [--abefhkmnptuvxBCHP] [-o option-name] [arg ...]
This is
This is
Question:
Is this the better way to use IFS? Best practice?
I'd like to dump the key/value pairs into an array and then call those out later in my script.
You can use your code by modifying it like this:
#!/bin/bash
KEY=/home/myusr/.keyinfo
IFS=$' ' # omitting this line will do too, as IFS is defaulted to space
declare -a arr=($(cat $KEY))
echo "This is ${arr[0]}"
echo "This is ${arr[1]}"
Use declare to declare variables, not set.
-A option is for associative array, -a for indexed array.
Instead of using cat you should consider using: declare -a arr=($(< $KEY))
I know that in bash:
declare array=(hei{1,2}_{1,2})
will create an array with a list of elements:
echo ${array[*]}
hei1_1 hei1_2 hei2_1 hei2_2
but I would like to use variables in the array declaration,too:
var=1,2
declare array=(hei{$var}_{$var})
But it does not work:
echo ${array[*]}
hei{1,2}_{1,2}
Please help. I find very frustrating to specify something like hei{1,2}_{a,b,c}..n times..{hei,hei} in a code.
Thanks in advance
NOTE: It is possible in zsh without eval (got it from stackexchange.com):
e.g., this script would do what i need, but in zsh (which I cannot always use):
#!/usr/bin/zsh
var1=( a c )
var2=( 1 2 )
arr=($^var1$^var2)
printf "${arr[*]}"
Brace expansion cannot be used with variables unless you also use eval, because brace expansion occurs before parameter expansion does.
The safe approach (no eval) is thus to not use brace expansion at all:
var=1,2
IFS=, read -r -a var_values <<<"$var"
result=( )
for val1 in "${var_values[#]}"; do
for val2 in "${var_values[#]}"; do
result+=( "hei${val1}_${val2}" )
done
done
The unsafe approach is something like this:
var=1,2
eval "array=( hei{$var}_{$var} )"
DO NOT follow the latter practice unless you trust your inputs.
Could try something like this...
var=1,2
declare -a array
IFS=','
for item in ${var}; do
array[${#array[#]}]="$item";
done
I am giving my first steps in bash and would like to create a associative array and iterate it. My instinct would be to do this:
declare -A FILES=(['SOURCE']='Source/Core/Core.js' ['DIST']='dist/Core.js')
for file in "${!FILES[#]}"; do
echo $file - $FILES[$file]
done
But output is:
SOURCE - [SOURCE]
DIST - [DIST]
and no path as expected. What am I missing? and btw, is the declare -A required?
Demo: https://ideone.com/iQpjmj
Thank you
Place your expansions around braces. Without it, $FILES and $file would expand separately.
${FILES[$file]}
A good practice also is to place them around double quotes to prevent word splitting and possible pathname expansion:
echo "$file - ${FILES[$file]}"
Test:
$ declare -A FILES=(['SOURCE']='Source/Core/Core.js' ['DIST']='dist/Core.js')
$ for file in "${!FILES[#]}"; do echo "$file - ${FILES[$file]}"; done
SOURCE - Source/Core/Core.js
DIST - dist/Core.js