2

I am writing a script that compares the first 3 letters of every line (by using cut to get them) in a file to strings inside an Array. I have already looked around but the solutions i found didn't work on my system.

Right now It looks like this:

weekdays=([Mon]=1 [Tue]=1 [Wed]=1 [Thu]=1 [Fri]=1 [Sat]=1 [Sun]=1)
input="/Foo/Bar.log"

while read -r line
do

cutline="$(echo ${line} | cut -c 1-3"

if [[ ${weekdays["$cutline"]} ]]
then
echo "Match"
else
echo "No Match"
fi

done < ${input}

The Line gets cut properly but something during the test returns a false Positive since no matter what the first 3 Letters are it returns a "Match".

When I checked the script with -x it showed me, instead of the actual test that it instead used

[[ -n 1 ]]

And when I tested it with the [ ] expresion it showed a 1

Does it check for every single char in the array and not just the whole words, or is there something else wrong with it?

If there is no problem, is there another way to compare the first 3 letters of a line to everything insde an array before continuing with the next?

As a side note: I am indeed running Bash 4, so associative arrays should work

4

The basic error is that you aren't actually declaring an associative array:

$ weekdays=(["Mon"]=1 ["Tue"]=1 ["Wed"]=1 ["Thu"]=1 ["Fri"]=1 ["Sat"]=1 ["Sun"]=1)
$ echo ${weekdays[@]}
1
$ echo ${weekdays[0]}
1
$ echo ${weekdays[2]}

$

I'm not entirely sure how bash is dealing with it and why it just takes a 1, but I am sure that that is not an associative array. As explained in man bash (emphasis mine):

An indexed array is created automatically if any variable is assigned to using the syntax name[subscript]=value. The subscript is treated as an arithmetic expression that must evaluate to a number. To explicitly declare an indexed array, use declare -a name (see SHELL BUILTIN COM‐ MANDS below). declare -a name[subscript] is also accepted; the sub‐ script is ignored.

Associative arrays are created using declare -A name.

So, try this instead and it will work as you expect:

declare -A weekdays=(["Mon"]=1 ["Tue"]=1 ["Wed"]=1 ["Thu"]=1 ["Fri"]=1 ["Sat"]=1 ["Sun"]=1)

That said, your script is a bit more complex than you need. Here's a simpler version using the same approach:

#!/bin/bash
declare -A weekdays=(["Mon"]=1 ["Tue"]=1 ["Wed"]=1 ["Thu"]=1 ["Fri"]=1 ["Sat"]=1 ["Sun"]=1)
input="/Foo/Bar.log"

cut -c 1-3 "$input" | while read -r line; do
    if [[ ${weekdays["$line"]} ]]
    then
            echo "Match : $cutline : ${weekdays[$line]}"
    else
            echo "No Match"
    fi
done    

Although I would probably do it like this:

#!/bin/bash
cut -c 1-3 "$1" | while read -r line; do
        case $line in
        "Mon"|"Tue"|"Wed"|"Thu"|"Fri"|"Sat"|"Sun")
                        echo yes;;
                *)
                        echo no;;
        esac
done

Then, run the script with the target file name as an argument:

script.sh /Foo/Bar.log"
  • Thank you very much, everything works as intended now. I made the mistake of not reading properly apparently. Also, the reason my script is a bit more complicated than it has to be is because of the fact that I need to output the Lines that do not have a specified string in the array. I should've probably mentioned that in the actual question now that I think about it. – Benjo Sep 23 '16 at 9:19
  • @Benjo that makes no difference. Just put the echo in the *) case block above. Of course, you could also do the whole thing with grep: grep -Ev "Mon|Tue|Wed|Thu|Fri|Sat|Sun" foo.log or, for matching lines grep -E "Mon|Tue|Wed|Thu|Fri|Sat|Sun" foo.log – terdon Sep 23 '16 at 9:21
  • on my end it does make a difference, it ouputs the first 3 letters instead of the whole Line that I need to output, hence why i saved it in an additional variable in my version. The reason i didn't want to use Grep is that in the log files I am letting this run through, there could potentially be different words beginning with "Mon, Tue" etc. hence why grep fell away for me, unless there is a way for grep to be specified (like ^\ in awk and sed) to look for the beginning of the line . – Benjo Sep 23 '16 at 9:40
  • 1
    As an array, a=(["Mon"]=1) sets the array of indice $Mon (0 if unset) to 1. Mon=2 bash -c 'a=(["Mon"]=1); typeset -p a' outputs declare -a a='([2]="1")' – Stéphane Chazelas Sep 23 '16 at 9:42
  • 1
    @Benjo yes grep has anchors ;either grep -E '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' like terdon showed, or simple disjunction can also be done with multiple arguments grep -e ^Mon -e ^Tue -e ^Wed -e ^Thu -e ^Fri -e ^Sat -e ^Sun. In either case add -v to get non-matching lines. – dave_thompson_085 Sep 23 '16 at 15:46
3

I'd use one invocation of a text-processing tool to process text, not several tools for each line of input:

awk -v 'weekday=(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' '
  {print ($0 ~ "^" weekday ? "" : "No ") "Match"}' < "$input"

You'd use a loop if you needed to run a particular application for each line of the input, but if it's just text processing like outputting the line to some file, then awk can do it like:

awk -v 'weekday=Mon|Tue|Wed|Thu|Fri|Sat|Sun' '
  (day = substr($0, 1, 3)) ~ weekday {
    print substr($0, 4) > day ".txt"
  } < "$input"
  • I can see now that my script would be conisdered bad practice because of inefficency and some other reasons, but using awk here won't offer me the possibility of using more commands after a match, i am using If for the sole reason of wanting to set some variables and outputting lines into a file after the script has found a match. I did not put this into my actual question so i can understand where this is coming from. If there is however a way for me to do this while still using awk to have better performance, please let me know – Benjo Sep 23 '16 at 9:53
  • @Benjo, see edit. – Stéphane Chazelas Sep 23 '16 at 10:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.