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

    2 Answers 2

    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" 
    7
    • 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
      CommentedSep 23, 2016 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
      CommentedSep 23, 2016 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
      CommentedSep 23, 2016 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")'CommentedSep 23, 2016 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.CommentedSep 23, 2016 at 15:46
    4

    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" 
    2
    • 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
      CommentedSep 23, 2016 at 9:53
    • @Benjo, see edit.CommentedSep 23, 2016 at 10:16

    You must log in to answer this question.

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.