Inline expansion of arrays in bash - arrays

I'm trying to expand an array in bash:
FILES=(2009q{1..4})
echo ${FILES[#]}
echo ${FILES[#]}.zip
Output is:
2009q1 2009q2 2009q3 2009q4
2009q1 2009q2 2009q3 2009q4.zip
But how can I expand the last line as in echo 2009q{1..4}.zip expansion, so that the last line looked like:
2009q1.zip 2009q2.zip 2009q3.zip 2009q4.zip
... but using array FILES?

FILES=(2009q{1..4})
echo ${FILES[#]/%/.zip}
Output:
2009q1.zip 2009q2.zip 2009q3.zip 2009q4.zip
From Bash's Parameter Expansion:
${parameter/pattern/string}: The pattern is expanded to produce a pattern just as in filename expansion. Parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with '/', all matches of pattern are replaced with string. Normally only the first match is replaced. If pattern begins with '#', it must match at the beginning of the expanded value of parameter. If pattern begins with '%', it must match at the end of the expanded value of parameter. If string is null, matches of pattern are deleted and the / following pattern may be omitted. If parameter is '#' or '', the substitution operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with '#' or '', the substitution operation is applied to each member of the array in turn, and the expansion is the resultant list.

You can use printf:
printf "%s.zip " "${FILES[#]}"
2009q1.zip 2009q2.zip 2009q3.zip 2009q4.zip

Related

bash 5.1.4 associative array with period in index works for one statement but not in another

As the title says, my index with a period in the name is properly set in one call to the function but not when referencing the index to assign a value. I only found one other question and the answer was to declare the array as associative. (Which I already had.)
The relevant part of the script:
#!/usr/bin/env bash
declare -Ag root_dict sub_dict1 sub_dict2
object=0.7.0-rc1 #parsed from file
_set_key $object
# called from another part of script
_set_key() {
if [[ -z $last_obj ]]; then # first object parsed.
root_dict_key="$object"
last_obj="$object"
last_key="$object"
object=""
root_dict[$root_dict_key]="" # i haven't parsed the value yet
last_dict=root_dict
last_key=root_dict_key
return
elif [[ -n $sub_dict ]]; then
# parsed another object to get the array value.
# value is also a key, to reference another array.
object=files # argument sent with caller
# indirect referencing because the referenced array \
dict_key="${!last_dict}" # is dynamic
dict_key[$last_key]="$object"
last_obj="$object"
last_val="$object"
object=""
return
fi
}
The first if statement properly sets the key. root_dict[0.7.0-rc1]=""
When I go to set the value for that key in the next call to _set_key and the elif statement I get:
line 136: 0.7.0-rc2: syntax error: invalid arithmetic operator (error token is ".7.0-rc2")
I know variable names can't have punctuation except for the underscore, but the other answer suggested that associative array indices are string literals. Why would the index get properly set in the first call but return the error in the second call? The caller is the same in both instances first sending object=0.7.0-rc1 and then object=files.
Looking at your code maybe you wanted to write
dict_key="${!last_dict}"
root_dict_key[$last_key]="$object"
... instead of ...
dict_key="${!last_dict}"
dict_key[$last_key]="$object"
dict_key was not declare as an associative array. When bash sees the assignment dict_key[$last_key]="$object" (note the […]) then the non-associative dict_key is interpreted as a regular array.
The braces of regular arrays open an arithmetic context. Bash arithmetic contexts cannot interpret floating point numbers, hence the error message.

Splitting a string by colon

I am trying to split the string by ':' and store it in an array, so something that looks like a:b:c:d:x:y:z will be stored in an array which holds, a, b, c, d, x, y, z as elements.
What I have written is
IFS = ':' read - r -a ARR <<< "$INFO"
where INFO is a string which is being read in from a file containing multiple strings in the aforementioned format.
I get an error saying "IFS: command not found".
I am reading them in this way:
while read INFO
Lastly when I try to assign the first element in the array to a variable, I am getting an error:
export NAME = $INFO[0]
the two errors I get here are export: '=' not a valid identifier and export: '[0]: not a valid identifier
I am relatively a newcomer to bash.
The basic problem here is that your code contains spaces in places where they aren't allowed. For instance, the following is perfectly fine syntax (though it fails to comply with POSIX conventions on variable naming, which advises lowercase characters be used for application-defined names):
info_string='a:b:c:d:x:y:z'
IFS=: read -r -a info_array <<< "$info_string"
Similarly, on a dereference, you need curly braces, and (again) can't put spaces around the =:
name=${info_array[0]}
This works:
s=a:b:c:d #sample string
IFS=:
a=( $s ) #array
printf "'%s' " "${a[#]}" #prints 'a' 'b' 'c' 'd'
The syntax to get the n-th item in an array is
${array_name[$index]}
(The curlies are required), so you need export NAME="${INFO[0]}" (assignments normally don't need to be quoted, however with export, declare, local, and similar, it's better to quote).
https://www.lukeshu.com/blog/bash-arrays.html is a good tutorial on how bash arrays work.

How do I filter elements in an array that both match a pattern and don't match a second pattern?

I'm using Ruby 2.4. I have the following expression for only keeping elements in an array that match a certain pattern:
lines.grep(/^[[:space:]]*\d+/)
How do I write a Ruby expression that keeps elements in an array that both match a pattern and don't match a second pattern? That is, I want to keep elements that match the above, but then also exclude elements that match:
/^[[:space:]]*\d+[:.]/
If my array contains only the element " 123 23:25 ", the result should contain this original element because it starts with a number that doesn't contain a ":" or "." after it.
Use the following regex:
/^[[:space:]]*\d++(?![:.])/
See this regex demo.
Here, ^[[:space:]]* part is the same as in the first regex, \d++ will possessively match 1+ digits (thus, deactivating backtracking) and (?![:.]) will fail any match if those 1+ digits are followed with either : or ..
Details:
^ - start of a line
[[:space:]]* - (may be replaced with \s*) - 0+ whitespace characters
\d++ - 1+ digits matches possessively so that backtracking into the pattern was not possible
(?![:.]) - a negative lookahead that fails the match if : or . is found immediately to the right of the current location (after 1+ digits).
To answer the general case of your question.
Try this
lines.select { |each| each =~ first_pattern && each !~ second_pattern }
Or if you use Ruby 2.3+
lines.grep(first_pattern).grep_v(second_pattern)
The name of grep_v is inspired by the grep -v command line options.

How can zsh array elements be transformed in a single expansion?

Say you have a zsh array like:
a=("x y" "v w")
I want to take the first word of every element, say:
b=()
for e in $a; {
b=($b $e[(w)0])
}
So now I have what I need in b:
$ print ${(qq)b}
'x' 'v'
Is there a way to do this in a single expansion expression? (i.e. not needing a for loop for processing each array element and accumulating the result in a new array).
It could be possible to take the word by removing from the first occurrence of a white space to the end in each element of the array like this:
$ print ${(qq)a%% *}
'x' 'v'
It coulde be noted that the%% expression (and some others) could be used for array elements:
In the following expressions, when name is an array and the substitution is not quoted, or if the ‘(#)’ flag or the name[#] syntax is used, matching and replacement is performed on each array element separately.
...
${name%pattern}
${name%%pattern}
If the pattern matches the end of the value of name, then substitute the value of name with the matched portion deleted; otherwise, just substitute the value of name. In the first form, the smallest matching pattern is preferred; in the second form, the largest matching pattern is preferred.
-- zshexpn(1): Expansion, Parameter Expansion

BASH Changing Array element into Variable

I'm trying to create variables from array elements and check value of my created variable in sourcefile.
MySourceFile:
value1abc="10"
value2efg="30"
value3ade="50"
value4tew="something else"
onemorevalue="192.168.1.0"
Script Code :
declare -a CfgData
CfgData=("value1abc" "value2efg" "value3ade" "value4tew" "othervalue" "onemorevalue")
COUNTER="0"
source MySourceFile
until [ $COUNTER == ${#CfgData[#]} ]; do
value=$[${CfgData[$COUNTER]}]
echo -ne "\\n${CfgData[$COUNTER]}=\"$value\""\\n\\n
let COUNTER+=1
done
Script works fine until it comes to values which contain data other than pure numbers (letters, spaces, dots, generally all characters) in which case :
value="randomcharacters" # Gives me "0"
value="something else" & value="192.168.1.0" # line XX: 192.168.1.0: syntax error: invalid arithmetic operator (error token is ".168.1.0")
I'm pretty sure I'm missing something elementary but I cannot figure it out now :P.
First, the $[...] bash syntax is obsolete and should not be used.
Now, it evaluates its content as an arithmetic expression. When you put in a string, is is interpreted as a variable, and its value again evaluated as an arithmetic expression. When a variable is unset, it evaluates to zero.
If you put a string with a single word, these rules make it expand to zero. If you put in a string with multiple words, the expansion fails, because the words are interpreted as variables, with no intermediate arithmetic operator, which is an error. This is what you see.
What you probably want, is replace the line value=$[${CfgData[$COUNTER]}] with value=${!CfgData[$COUNTER]}.
Let me add that your script would probably be better written without the indices, like
for varname in "${CfgData[#]}"; do
value=${!varname}
echo -ne "$varname=$value"
done

Resources