Why is my for loop only storing all desired items as one element? - bash

I am trying to figure out why all items are being stored as one element:
filedates=($dirPath/*.csv)
filebasenames=()
filedates2=()
for file in ${filedates[#]}; do
filebasenames+="${file##*/} "
done
for i in ${filebasenames[#]}; do
filedates2+="$(echo $i | cut -c6-13) "
done
for i in ${filedates2[#]}; do
echo $i
done
echo test here ${filebasenames[0]}
echo test here ${filebasenames[1]}
Im confused because the third for loop prints each element in a new line so I assumed that there is more than one element in the array but when I echo the "test here" line it shows me all the elements in one line indicating that there is only one large string. I verify that with the second echo test here 2 line

You aren't appending to the array; you are only appending to the first element of the array. Appending to an array requires parentheses. (Notice, too, that I've dropped the space from the new array element.)
for file in ${filedates[#]}; do
filebasenames+=("${file##*/}")
done
That said, you don't need a loop at all; you can apply the ## to all the elements of the array in one operation.
filebasenames=("${filedates[#]##*/}")
The other array is probably still best populated using a loop.
for i in "${filebasenames[#]}"; do
filedates2+=("$(echo "$i" | cut -c6-13)")
done

Related

Bash. Associative array iteration (ordered and without duplicates)

I have two problems handling associative arrays. First one is that I can't keep a custom order on it.
#!/bin/bash
#First part, I just want to print it ordered in the custom created order (non-alphabetical)
declare -gA array
array["PREFIX_THIS","value"]="true"
array["PREFIX_IS","value"]="false"
array["PREFIX_AN","value"]="true"
array["PREFIX_ORDERED","value"]="true"
array["PREFIX_ARRAY","value"]="true"
for item in "${!array[#]}"; do
echo "${item}"
done
Desired output is:
PREFIX_THIS,value
PREFIX_IS,value
PREFIX_AN,value
PREFIX_ORDERED,value
PREFIX_ARRAY,value
But I'm obtaining this:
PREFIX_IS,value
PREFIX_ORDERED,value
PREFIX_THIS,value
PREFIX_AN,value
PREFIX_ARRAY,value
Until here the first problem. For the second problem, the order is not important. I added more stuff to the associative array and I just want to loop on it without duplicates. Adding this:
array["PREFIX_THIS","text"]="Text for the var"
array["PREFIX_IS","text"]="Another text"
array["PREFIX_AN","text"]="Text doesn't really matter"
array["PREFIX_ORDERED","text"]="Whatever"
array["PREFIX_ARRAY","text"]="More text"
I just want to loop over "PREFIX_THIS", "PREFIX_IS", "PREFIX_AN", etc... printing each one only once. I just want to print doing an "echo" on loop (order is not important for this part, just to print each one only once). Desired output:
PREFIX_ORDERED
PREFIX_AN
PREFIX_ARRAY
PREFIX_IS
PREFIX_THIS
I achieved it doing "dirty" stuff. But there must be a more elegant way. This is my working but not too much elegant approach:
already_set=""
var_name=""
for item in "${!array[#]}"; do
var_name="${item%,*}"
if [[ ! ${already_set} =~ "${var_name}" ]]; then
echo "${var_name}"
already_set+="${item}"
fi
done
Any help? Thanks.
Iteration Order
As Inian pointed out in the comments, you cannot fix the order in which "${!array[#]}" expands for associative arrays. However, you can store all keys inside a normal array that you can order manually.
keysInCustomOrder=(PREFIX_{THIS,IS,AN,ORDERED,ARRAY})
for key in "${keysInCustomOrder[#]}"; do
echo "do something with ${array[$key,value]}"
done
Unique Prefixes of Keys
For your second problem: a["key1","key2"] is the same as a["key1,key2"]. In bash, arrays are always 1D therefore there is no perfect solution. However, you can use the following one-liner as long as , is never part of key1.
$ declare -A array=([a,1]=x [a,2]=y [b,1]=z [c,1]=u [c,2]=v)
$ printf %s\\n "${!array[#]}" | cut -d, -f1 | sort -u
a
b
c
When your keys may also contain linebreaks delemit each key by null \0.
printf %s\\0 "${!array[#]}" | cut -zd, -f1 | sort -zu
Alternatively you could use reference variables to simulate 2D-arrays, however I would advice against using them.

Bash: nested loop one way comparison

I have one queston about nested loop with bash.
I have an input files with one file name per line (full path)
I read this file and then i make a nest loop:
for i in $filelines ; do
echo $i
for j in $filelines ; do
./program $i $j
done
done
The program I within the loop is pretty low.
Basically it compare the file A with the file B.
I want to skip A vs A comparison (i.e comparing one file with itslef) AND
I want to avoid permutation (i.e. for file A and B, only perform A against B and not B against A).
What is the simplest to perform this?
Version 2: this one takes care of permutations
#!/bin/bash
tmpunsorted="/tmp/compare_unsorted"
tmpsorted="/tmp/compare_sorted"
>$tmpunsorted
while read linei
do
while read linej
do
if [ $linei != $linej ]
then
echo $linei $linej | tr " " "\n" | sort | tr "\n" " " >>$tmpunsorted
echo >>$tmpunsorted
fi
done <filelines
done <filelines
sort $tmpunsorted | uniq > $tmpsorted
while read linecompare
do
echo "./program $linecompare"
done <$tmpsorted
# Cleanup
rm -f $tmpunsorted
rm -f $tmpsorted
What is done here:
I use the while loop to read each line, twice, i and j
if the value of the lines is the same, forget them, no use to consider them
if they are different, output them into a file ($tmpunsorted). And they are sorted in alphebetical order before going tothe $tmpunsorted file. This way the arguments are always in the same order. So a b and b a will be same in the unsorted file.
I then apply sort | uniq on $tmpunsorted, so the result is a list of individual argument pairs.
finally loop on the $tmpsorted file, and call the program on each individual pair.
Since I do not have your program, I did an echo, which you should remove to use the script.

How to print out individual numbers after ordering an array in Bash?

I'm new to bash and having some issues printing out individual numbers after sorting an array.
I have the following....
for x in ${array[#]}
do
echo $x
done| sort
This is the only way I was able to print out the entire array in order.
Now I'm trying to print out a single item after it's been ordered
so I tried....
for x in ${array[#]}
do
echo ${array[2]}
exit
done| sort
But it prints the third item in the unordered array array instead.
Any help?
You are printing the variable and then trying to sort the ONLY variable that you have printed (in your case ${array[2]})
Try this:
sorted=($(printf '%s\n' "${array[#]}"|sort))
echo ${sorted[2]}
This sorts the array and stores it in another array sorted

Reading numeric values from grep output in bash

I have a file filled by rows of text, I'm interested about a group of these, every line starts with the same word, in each line there are two numbers i have to elaborate later, and they are always in the same position, for example:
Round trip time was 49.9721 milliseconds in repetition 5 tcp_ping received 128 bytes back
I was thinking about trying to use grep to grab the rows wanted into a new file, and then put the content of this new file into an array, to easily access it during the elaboration, but this isn't working, any tips?
#!/bin/bash
InputFile="../data/N.dat"
grep "Round" ../data/tcp_16.out > "$InputFile"
IFS=' ' read -a array <<< "$InputFile"
If they're all you care about, you can read only the numbers in.
I'd also strongly suggest extracting the values you're going to be analyzing into arrays, like so, rather than storing the full lines as strings:
ms_time_arr=( ) # array: map repetitions to ms_time
bytes_arr=( ) # array: map repetitions to bytes
while read -r ms_time repetition bytes_back _; do
# log to stderr to show that we read the data
echo "At $ms_time ms, repetition $repetition, got $bytes_back back" >&2
ms_time_arr[$repetition]=$ms_time
bytes_arr[$repetition]=$bytes_back
done < <(grep -e 'Round' <../data/N.dat | tr -d '[[:alpha:]]')
# more logging, to show that array contents survive the loop
declare -p ms_time_arr bytes_arr
This works by using tr to remove all alpha characters, leaving only numbers, punctuation and whitespace.

bash find keyword in an associative array

I have incoming messages from a chat server that need to be compared against a list of keywords. I was using regular arrays, but would like to switch to associative arrays to try to increase the speed of the processing.
The list of words would be in an array called aWords and the values would be a 'type' indicator, i.e. aWords[damn]="1", with 1 being swear word in a legend to inform the user.
The issue is that I need to compare every index value with the input $line looking for substrings. I'm trying to avoid a loop thru each index value if at all possible.
From http://tldp.org/LDP/abs/html/string-manipulation.html, I'm thinking of the Substring Removal section.
${string#substring}
Deletes shortest match of $substring from front of $string.
A comparison of the 'removed' string from the $line, may help, but will it match also words in the middle of other words? i.e. matching the keyword his inside of this.
Sorry for the long-winded post, but I tried to cover all of what I'm attempting to accomplish as best I could.
# create a colon-separated string of the array keys
# you can do this once, after the array is created.
keys=$(IFS=:; echo "${!aWords[*]}")
if [[ ":$keys:" == *:"$word":* ]]; then
# $word is a key in the array
case ${aWords[$word]} in
1) echo "Tsk tsk: $word is a swear word" ;;
# ...
esac
fi
This is the first time I heard of associative arrays in bash. It inspired me to also try to add something, with the chance ofcourse that I completely miss the point.
Here is a code snippet. I hope I understood how it works:
declare -A SWEAR #create associative array of swearwords (only once)
while read LINE
do
[ "$LINE"] && SWEAR["$LINE"]=X
done < "/path/to/swearword/file"
while :
do
OUTGOING="" #reset output "buffer"
read REST #read a sentence from stdin
while "$REST" #evaluate every word in the sentence
do
WORD=${REST%% *}
REST=${REST#* }
[ ${SWEAR[$WORD]} ] && WORD="XXXX"
OUTGOING="$OUTGOING $WORD"
done
echo "$OUTGOING" #output to stdout
done

Resources