Splitting a string by colon - arrays

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.

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.

Why does concatenating arrays in Perl produce numbers?

I just tried to concatenate arrays in Perl with the + operator and got strange results:
perl -wE 'say([1, 2, 3] + [4, 5, 6])'
73464360
Doing the same with hashes seems to be a syntax error:
perl -wE 'say({} + {})'
syntax error at -e line 1, near "} +"
Execution of -e aborted due to compilation errors.
What is the result of the 1st expression? Is it documented anywhere?
It is from the numification of references, which produces the memory address of the reference.
perl -E 'say \#a; say 0+\#a; printf "%x\n",0+\#a'
Typical output (though it may change every time you run the program)
ARRAY(0x1470518)
21431576
1470518 <--- same number as in first line
Your hash reference example almost works, but it seems that Perl is parsing the first set of {} blocks as a code block rather than as a hash reference. If you use a unary + and force Perl to treat it as a hash reference, it will work. I mean "work".
perl -E 'say(+{} + {})'
40007168
Because + in Perl is an arithmetic operator only. It forces its arguments to be interpreted as numbers, no matter what. That's why Perl has a separate operator for string concatenation (.).
What you are doing, effectively, is adding the addresses where the arrays are stored.
Array concatenation is accomplished by simply listing the arrays, one after the other. However, if you are using references to arrays ([...]), then you have to dereference them first with #{...}:
perl -wE 'say( #{[1,2,3]}, #{[4,5,6]} )'
But normally you would use array variables and not need the extra syntax.
perl -wE 'my #a = (1,2,3); my #b = (4,5,6); say join("-",#a,#b)'
#=> 1-2-3-4-5-6
The same thing goes for hashes; my %c = (%a,%b); will combine the contents of %a and %b (in that order, so that %b's value for any common keys will overwrite %a's) into the new hash %c. You could use my $c = { %$a, %$b }; to do the same thing with references. One gotcha, which you are running into in your + attempt, is that {} may be interpreted as an empty block of code instead of an empty hash.

how to use RANDOM command in declared array

I am trying to build a script where I need to create a password generator with the following parameters :
must be at least 8 characters long but not longer than 16 characters.
must contain at least 1 digit (0-9).
must contain at least 1 lowercase letter.
must contain at least 1 uppercase letter.
must contain exactly one and only one of # # $ % & * + - =
I have two ideas :
The first :
#!/bin/bash
#
#Password Generator
#
#
Upper=('A''B''C''D''E''F''G''H''I''J'K''L''M''N''O''P''Q''R''S''T''U''V''W''X''Y''Z')
Lower=('a''b''c''d''e''z''f''g''h''i''j''k''l''m''o'''p''q''r''s''t''u''v''w''x''y''z')
Numbers=('1''2''3''4''5''6''7''8''9')
SpecialChar=('#''#''$''%''&''*''+''-''=')
if [ S# -eq 0 ] ; then
Pwlength=`shuf -i 8-16 -n 1`
Password=`< /dev/urandom tr -dc A-Za-z0-9$SpecialChar | head -c $Pwlength`
echo "Random Password is being generated for you"
sleep 5
echo "Your new password is : $Password"
exit
The problem is I get characters that I didnt even defined ?
The secound idea :
for((i=0;i<4;i++))
do
password=${Upper[$random % ${#Lower[#]} ] }
password=${Upper[$random % ${#Upper[#]} ] }
password=${Upper[$random % ${#Number[#]} ] }
password=${Upper[$random % ${#SpecialChar[#]} ] }
done
For some reason non of them work ;/
In your first example, move the "-" character to the end of the SpecialChar. I think the definition as you had it results in allowing "+" to "=" (i.e., the value passed to tr reads as "+-="), which accounts for the characters you were not expecting. Alternatively, replace the "-" with "_".
So, try a definition like:
SpecialChar=('#''#''$''%''&''*''+''=''-')
As already mentioned, it would be cleaner and easier to use directly a string to handle list of characters. Handling special characters in an array may cause side effects (for instance getting the list of files in the current directory with the '*' character). In addition, arrays may be difficult to pass as function arguments).
ALPHA_LOW="abcdefghijklmnopqrstuvwxyz"
# Then simply access the char in the string at the ith position
CHAR=${ALPHA_LOW:$i:1}
You could generate upper cases from the lower cases.
ALPHA_UP="`echo \"$ALPHA_LOW\" | tr '[:lower:]' '[:upper:]'`"
The variable which contains a random number is $RANDOM (in capital letters).
sleep 5 is unnecessary.
You need to find a way to keep count of occurrences left for each character type. For more information, I wrote a complete script to do what you described here.
Your first attempt has the following problems:
You are using arrays to contain single strings. Pasting 'A''B''C' is equivalent to 'ABC'. Simply converting these to scalar variables would probably be the simplest fix.
You are not quoting your variables.
You declare Upper, Lower, and Numbers, but then never use them.
Using variables for stuff you only ever use once (or less :-) is dubious anyway.
As noted by #KevinO, the dash has a special meaning in tr, and needs to be first or last if you want to match it literally.
On top of that, the sleep 5 doesn't seem to serve any useful purpose, and will begin to annoy you if it doesn't already. If you genuinely want a slower computer, I'm sure there are people here who are willing to trade.
Your second attempt has the following problems:
The special variable $RANDOM in Bash is spelled in all upper case.
You are trying to do modulo arithmetic on (unquoted!) arrays of characters, which isn't well-defined. The divisor after % needs to be a single integer. You can convert character codes to integers but it's sort of painful, and it's less than clear what you hope for the result to be.
A quick attempt at fixing the first attempt would be
Password=$(< /dev/urandom tr -dc 'A-Za-z0-9##$%&*+=-' | head -c $(shuf -i 8-16 -n 1))
If you want to verify some properties on the generated password, I still don't see why you would need arrays.
while read -r category expression; do
case $Password in
*[$expression]*) continue;;
*) echo "No $category"; break;;
esac
done <<'____HERE'
lowercase a-z
uppercase A-Z
numbers 0-9
specials -##$%&*+=
HERE

For loop to take the value of the whole array each time

Suppose I have 3 arrays, A, B and C
I want to do the following:
A=("1" "2")
B=("3" "4")
C=("5" "6")
for i in $A $B $C; do
echo ${i[0]} ${i[1]}
#process data etc
done
So, basically i takes the value of the whole array each time and I am able to access the specific data stored in each array.
On the 1st loop, i should take the value of the 1st array, A, on the 2nd loop the value of array B etc.
The above code just iterates with i taking the value of the first element of each array, which clearly isn't what I want to achieve.
So the code only outputs 1, 3 and 5.
You can do this in a fully safe and supportable way, but only in bash 4.3 (which adds namevar support), a feature ported over from ksh:
for array_name in A B C; do
declare -n current_array=$array_name
echo "${current_array[0]}" "${current_array[1]}"
done
That said, there's hackery available elsewhere. For instance, you can use eval (allowing a malicious variable name to execute arbitrary code, but otherwise safe):
for array_name in A B C; do
eval 'current_array=( "${'"$array_name"'[#]}"'
echo "${current_array[0]}" "${current_array[1]}"
done
If the elements of the arrays don't contain spaces or wildcard characters, as in your question, you can do:
for i in "${A[*]}" "${B[*]}" "${C[*]}"
do
iarray=($i)
echo ${iarray[0]} ${iarray[1]}
# process data etc
done
"${A[*]}" expands to a single string containing all the elements of ${A[*]}. Then iarray=($i) splits this on whitespace, turning the string back into an array.

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