Array from a file - bash

If I have a file like this:
A:a
B:b
C:c
I need to create 2 arrays like
one=('A' 'B' 'C')
two=('a' 'b' 'c')
How can I do it in bash?
I've tried this:
declare -a one
declare -a two
while read line
do
IFS=':' read -ra ADDR <<< $line
echo ${ADDR[0]}
echo ${ADDR[1]}
done < file.txt
Sorry I wrote from work and then I came home. Sorry again. The problem with this is that it's printing
littlelion:Documents dierre$ sh prova.sh
A a
B b
so it's missing C c and I have no idea how to add an element to an array

Quotes fix everything:
while read line
do
IFS=':' read -ra ADDR <<< "$line"
echo ${ADDR[0]}
echo ${ADDR[1]}
done < file.txt
Quoting the variable "$line" is what made the difference. If you're not getting the line with "C:c", it's probably because your file is missing a final newline.

If I understand what you're trying to do properly, this should work:
one=()
two=()
while IFS=: read new_one new_two || [ -n "$new_one" ]; do
one+=("$new_one")
two+=("$new_two")
done
echo "one:" "${one[#]}"
echo "two:" "${two[#]}"
Note: I agree with #Dennis Williamson that the final line isn't being processed because it doesn't end with a newline; I added the || [ -n "$new_one" ] bit to keep this from being a problem.

Use Command Substitution:
declare -a one
declare -a two
one=( $(cut -d: -f1 file) )
two=( $(cut -d: -f2 file) )
var=$(command) captures the output of your command and assigns it to var. The outer parens makes that an array assignment. cut -d: -f1 says to treat your file as a colon delimited file and print the first field. cut -d: -f2 does the same but it prints the second field.
Edit in response to OP's edits
You can read into ADDR directly like so:
declare -a ADDR
while IFS=':' read -a ADDR; do
echo ${ADDR[0]}
echo ${ADDR[1]}
done < file.txt
Although that won't populate arrays one and two...

Related

How to use two `IFS` in Bash

So I know I can use a single IFS in a read statement, but is it possible to use two. For instance if I have the text
variable = 5 + 1;
print variable;
And I have the code to assign every word split to an array, but I also want to split at the ; as well as a space, if it comes up.
Here is the code so far
INPUT="$1"
declare -a raw_parse
while IFS=' ' read -r -a raw_input; do
for raw in "${raw_input[#]}"; do
raw_parse+=("$raw")
done
done < "$INPUT"
What comes out:
declare -a raw_parse=([0]="variable" [1]="=" [2]="5" [3]="+" [4]="1;" [5]="print" [6]="variable;")
What I want:
declare -a raw_parse=([0]="variable" [1]="=" [2]="5" [3]="+" [4]="1" [5]=";" [6]="print" [7]="variable" [8]=";")
A workaround with GNU sed. This inserts a space before every ; and replaces every newline with a space.
read -r -a raw_input < <(sed -z 's/;/ ;/g; s/\n/ /g' "$INPUT")
declare -p raw_input
Output:
declare -a raw_input=([0]="variable" [1]="=" [2]="5" [3]="+" [4]="1" [5]=";" [6]="print" [7]="variable" [8]=";")

Bash Associative Array from String?

A command emits the string: "[abc]=kjlkjkl [def]=yutuiu [ghi]=jljlkj"
I want to load a bash associative array using these key|value pairs, but the result I'm getting is a single row array where the key is formed of the first pair [abc]=kjlkjkl and the value is the whole of the rest of the string, so: declare -p arr returns declare -A arr["[abc]=kjlkjkl"]="[def]=yutuiu [ghi]=jljlkj"
This is what I am doing at the moment. Where am I going wrong please?
declare -A arr=()
while read -r a b; do
arr["$a"]="$b"
done < <(command that outputs the string "[abc]=kjlkjkl [def]=yutuiu [ghi]=jljlkj")
You need to parse it: split the string on spaces, split each key-value pair on the equals sign, and get rid of the brackets.
Here's one way, using tr to replace the spaces with newlines, then tr again to remove all brackets (including any that occur in a value), then IFS="=" to split the key-value pairs. I'm sure this could be done more effectively, like with AWK or Perl, but I don't know how.
declare -A arr=()
while IFS="=" read -r a b; do
arr["$a"]="$b"
done < <(
echo "[abc]=kjlkjkl [def]=yutuiu [ghi]=jljlkj" |
tr ' ' '\n' |
tr -d '[]'
)
echo "${arr[def]}" # -> yutuiu
See Cyrus's answer for another take on this, with the space and equals steps combined.
Append this to your command which outputs the string:
| tr ' =' '\n ' | tr -d '[]'
You can use the "eval declare" trick - but be sure your input is clean.
#! /bin/bash
s='[abc]=kjlkjkl [def]=yutuiu [ghi]=jljlkj'
eval declare -A arr=("$s")
echo ${arr[def]} # yutuiu
If the input is insecure, don't use it. Imagine (don't try) what would happen if
s='); rm -rf / #'
The "proper" good™ solution would be to write your own parser and tokenize the input. For example read the input char by char, handle [ and ] and = and space and optionally quoting. After parsing the string, assign the output to an associative array.
A simple way could be:
echo "[abc]=kjlkjkl [def]=yutuiu [ghi]=jljlkj" |
xargs -n1 |
{
declare -A arr;
while IFS= read -r line; do
if [[ "$line" =~ ^\[([a-z]*)\]=([a-z]*)$ ]]; then
arr[${BASH_REMATCH[1]}]=${BASH_REMATCH[2]}
fi
done
declare -p arr
}
outputs:
declare -A arr=([abc]="kjlkjkl" [ghi]="jljlkj" [def]="yutuiu" )

bash while read loop arguments from variable issue

I have a bash script with following variable:
operators_list=$'andrii,bogdan,eios,fre,kuz,pvm,sebastian,tester,tester2,vincent,ykosogon'
while IFS=, read -r tech_login; do
echo "... $tech_login ..."
done <<< "$operators_list"
I need to read arguments from variable and work with them in loop. But it returns echo only one time with all items:
+ IFS=,
+ read -r tech_login
+ echo '... andrii,bogdan,eios,fre,kuz,pvm,sebastian,tester,tester2,vincent,ykosogon ...'
... andrii,bogdan,eios,fre,kuz,pvm,sebastian,tester,tester2,vincent,ykosogon ...
+ IFS=,
+ read -r tech_login
What am I doing wrong? How to rework script, so it will work only with one item per time?
operators_list=$'andrii,bogdan,eios,fre,kuz,pvm,sebastian,tester,tester2,vincent,ykosogon'
So you have strings separated by ,. You can do that multiple ways:
using bash arrays:
IFS=, read -a operators <<<$operators_list
for op in "${operators[#]}"; do
echo "$op"
done
Using a while loop, like you wanted:
while IFS= read -d, -r op; do
echo "$op"
done <<<$operators_list
Using xargs, because why not:
<<<$operators_list xargs -d, -n1 echo
The thing with IFS and read delimeter is: read reads until delimeter specified with -d. Then after read has read a full string (usually whole line, as default delimeter is newline), then the string is splitted into parts using IFS as delimeter. So you can:
while IFS=: read -d, -r op1 op2; do
echo "$op1" "$op2"
done <<<"op11:op12,op12:op22"

Creating a bash array, separated by new lines

I am reading in from a .txt file which looks something like this:
:DRIVES
name,server,share_1
other_name,other_server,share_2
new_name,new_server,share 3
:NAME
which is information to mount drives. I want to load them into a bash array to cycle through and mount them, however my current code breaks at the third line because the array is being created by any white space. Instead of reading
new_name,new_server,share 3
as one line, it reads it as 2 lines
new_name,new_server,share
3
I have tried changing the value of IFS to
IFS=$'\n' #and
IFS='
'
however neither work. How can I create an array from the above file separated by newlines. My code is below.
file_formatted=$(cat ~/location/to/file/test.txt)
IFS='
' # also tried $'\n'
drives=($(sed 's/^.*:DRIVES //; s/:.*$//' <<< $file_formatted))
for line in "${drives[#]}"
do
#seperates lines into indiviudal parts
drive="$(echo $line | cut -d, -f2)"
server="$(echo $line | cut -d, -f3)"
share="$(echo $line | cut -d, -f4 | tr '\' '/' | tr '[:upper:]' '[:lower:]')"
#mount //$server/$share using osascript
#script breaks because it tries to mount /server/share instead of /server/share 3
EDIT:
tried this and got the same output as before:
drives=$(sed 's/^.*:DRIVES //; s/:.*$//' <<< $file_formatted)
while IFS= read -r line; do
printf '%s\n' "$line"
done <<< "$drives"
This is the correct way to iterate over your file; no arrays needed.
{
# Skip over lines until we read :DRIVES
while IFS= read -r line; do
[[ $line = :DRIVES ]] && break
done
# Split each comma-separated line into the desired variables,
# until we read :NAMES, wt which point we break out of this loop
while IFS=, read -r drive server share; do
[[ $drive == :NAMES ]] && break
# Use $drive, $server, and $share
done
# No need to read the rest of the file, if any
} < ~/location/to/file/test.txt

bash read loop only reading first line of input variable

I have a read loop that is reading a variable but not behaving the way I expect. I want to read every line of my variable and process each one. Here is my loop:
while read -r line
do
echo $line | sed 's/<\/td>/<\/td>$/g' | cut -d'$' -f2,3,4 >> file.txt
done <<< "$TABLE"
I expect it to process every line of the file but instead it just does the first one. If my the middle is simply echo $line >> file.txt it works as expected. What's going on here? How do I get the behavior I want?
It seems your lines are delimited by \r instead of \n.
Use this while loop to iterate the input with use of read -d $'\r':
while read -rd $'\r' line; do
echo "$line" | sed 's~</td>~</td>$~g' | cut -d'$' -f2,3,4 >> file.txt
done <<< "$TABLE"
If $TABLE contains a multi-line string, I recommend
printf '%s\n' "$TABLE" |
while read -r line; do
echo $line | sed 's/<\/td>/<\/td>$/g' | cut -d'$' -f2,3,4 >> file.txt
done
This is also more portable since the '<<<' operator for here-strings is not POSIX.

Resources