Search code examples
arraysbashlogginggrepecho

Bash strings getting cut off when outputting from array at space marks


So I am going through a log file and extracting information from said log file such as device name, location, and time of log. It looks something like the following:

edge2.mco1-9 Jun 11:32:24 GMT %SEC_LOGIN-5-LOGIN_SUCCESS: Login Success

I am coming across an issue where when I run the following:


for y in ${DeviceName[@]}
do
    echo "Device: $y"
    counter=1
    for z in ${LocationName[@]}
    do
        testing=($(grep "$y\.$z\-.*\%" hacker.txt | cut -f2 -d "-" | cut -f1 -d% | head -n 1))
        if [[ ${testing[$counter]} =~ [^0-9] ]]
        then
            echo $z
            echo $testing
        fi
        
    done
    
done

the day number is the only part outputted. DeviceName is an array of device names. LocationName is an array of location names. The complicated part was due to me wanting to only output the location name if it had something coming from the grep since I did not want a long list of locations that had no time attached to it. I did experiment with saving to a .txt file, which did do the correct time, but it seems outputting the correct time is more troublesome due to spaces. Using the example,

9 Jun 11:32:24 GMT

should be the output. Is there any way to get around this? The $testing is kept as attempts to do ${testing[$counter]}, $(${testing[$counter]}), and other methods did not work giving me errors. Errors are Jun and ./finder: line 113: Jun: command not found respectively $testing at least gives me the day number. Obviously there is an issue with spaces but I don't know how to fix this.


Solution

  • Setup:

    $ cat hacker.txt
    edge2.mco1-9 Jun 11:32:24 GMT %SEC_LOGIN-5-LOGIN_SUCCESS: Login Success
    $ y=edge2
    $ z=mco1
    

    The grep command:

    $ grep "$y\.$z\-.*\%" hacker.txt | cut -f2 -d "-" | cut -f1 -d% | head -n 1                                                                    
    9 Jun 11:32:24 GMT
    

    Loading this into the testing[] array:

    $ testing=( $(grep "$y\.$z\-.*\%" hacker.txt | cut -f2 -d "-" | cut -f1 -d% | head -n 1) )
    $ typeset -p testing
    declare -a testing=([0]="9" [1]="Jun" [2]="11:32:24" [3]="GMT")
    

    Running the test:

    $ counter=1
    $ [[ ${testing[$counter]} =~ [^0-9] ]] && echo "success"
    success
    
    # turn on debugging to see what we're testing:
    $ set -xv                                                  # turn on debugging
    
    $ [[ ${testing[$counter]} =~ [^0-9] ]] && echo "success"
    [[ ${testing[$counter]} =~ [^0-9] ]] && echo "success"
    + [[ Jun =~ [^0-9] ]]                                      # contents of testing[1] == 'Jun'
    + echo success
    success
    
    $ set +xv                                                  # turn off debugging
    

    So far so good.

    OP has stated the desired output should look like:

    9 Jun 11:32:24 GMT
    

    But the current echo command has an issue:

    echo $testing                                            
    

    $testing is an array; without an index bash treats $testing the same as ${testing[0]} which in this case outputs 9 (see the output from typeset -p testing - above); to output the entire contents of the array OP can use:

    $ echo "${testing[@]}"
    9 Jun 11:32:24 GMT
    

    Net result: OP should be able to change echo $testing to echo "${testing[@]}" to obtain the desired output.


    OP has mentioned the code complication (loading testing[] array, comparing testing[1] ???) is due to only printing output if grep finds a match.

    We can streamline the code a bit by replacing the guts of the for z loop with:

        # no need for array, just capture datetime stamp:
    
        dtstamp=$(grep "$y\.$z\-.*\%" hacker.txt | cut -f2 -d "-" | cut -f1 -d% | head -n 1)
    
        # is length of 'dtstamp' non-zero?
    
        if [[ -n "${dtstamp}" ]]
        then
            echo "${z}"
            echo "${dtstamp}"
        fi
    

    If the objective is to only print ${z} when grep finds a match (ie, OP doesn't need to print ${dtstamp}) we can streamline the code a bit more to where the only command in the for z loop is:

    grep "$y\.$z\-.*\%" hacker.txt >/dev/null && echo "${z}"
    

    Demo:

    $ z=mco1
    $ grep "$y\.$z\-.*\%" hacker.txt >/dev/null && echo "${z}"
    mco1
    
    $ z=xxxx
    $ grep "$y\.$z\-.*\%" hacker.txt >/dev/null && echo "${z}"
                  <<<=== no output
    

    NOTES:

    • can also remove the counter=1 from the current code
    • OP should consider cutting-n-pasting their code (along with the appropriate shebang, eg, #!/bin/bash) into shellcheck.net; this will report on syntax issues as well as make some suggestions re: 'best practices'