1

I have declared an array called arrUsers in my shell script. I am trying to get all the users who have logged onto a linux server using the last command. I want to do something like this:

declare -a arrUsers=()
for user in `last | awk '{print $1}'
do
    if $user not in arrUsers()
        do arrUsers += users

I am not quite sure what the correct syntax to do this is. Any help would be greatly appreciated.

3
  • 2
    If you your aim is to "deduplicate" the output of last, the awk invocation you already have seems a more natural place than a shell loop. (And note that last (at least the one I have, from util-linux 2.37) also emits lines that don't start with a user name). Are you interested in how to keep the elements of an array unique while adding new ones, regardless of the specific example in your question? – fra-san Jun 17 at 22:04
  • Yes, I also would like to know how to do that – user477921 Jun 21 at 16:20
  • The answer I posted tries to address all the points in my previous comment, including how to avoid adding duplicate elements to an array - see the nested loops snippet in point 2. – fra-san Jun 21 at 16:28
2

I see two distinct points here: getting a list of users that successfully logged in (1) and adding that list to a Bash array (2).

  1. Parsing last's output is not straightforward, because it also contains lines that do not start with a user name. A few alternatives you have on Linux:

    lslogins -r | awk -F '[ ]' 'NR > 1 && $6 { print $2 }'
    

    lslogins displays information about users (not login events), hence it only outputs one line per user. The -r option tells it not to columnate the output, allowing us to assume a single space as a column separator and make use of awk's ability to avoid squeezing space characters when FS is not a single space (the default). I am not sure, though, that no space characters will appear in the content of any of lslogins's output fields.

    Alternatively:

    utmpdump /var/log/wtmp 2>/dev/null |
      gawk -v FPAT='\\[[^][]*\\]' '
        $1 == "[7]" { gsub(/[][]/,"",$4); sub(/ +$/,"",$4); users[$4] }
        END { for (user in users) print user }'
    

    This uses a non-standard GNU AWK's feature to split records based on a regular expression (stored in FPAT) describing the fields' content. In utmpdump's output, fields are wrapped in square brackets and separated by a single space. We filter records based on their type, stored in the first field (see man 5 wtmp for more on this).

    Note that, while creating such names would definitely not be an advisable choice, Linux allows user names not to conform to the standard and include characters not in the portable filename character set — including spaces. Space characters would not be an issue with lslogins -r, which escapes them (as \x20), but trailing spaces would not be unambiguously represented in utmpdump's output and the above code snippet removes them.

  2. You can place a list of unique user names in an array using:

    readarray -t users \
      < <(lslogins -r | awk -F '[ ]' 'NR > 1 && $6 { print $2 }')
    

    Or, to add elements to an existing array, without resetting it:

    readarray -t -O "${#users[@]}" users \
      < <(lslogins -r | awk -F '[ ]' 'NR > 1 && $6 { print $2 }')
    

    These do not take care of keeping elements unique, though. To achieve that, you will probably need a couple of loops:

    while IFS= read -r user
    do
      for el in "${users[@]}"
      do
        if [ "$el" = "$user" ]
        then
          continue 2
        fi
      done
      users+=( "$user" )
    done < <(lslogins -r | awk -F '[ ]' 'NR > 1 && $6 { print $2 }')
    

    As an alternative (if your Bash version supports it), you may use an associative array and store user names as keys, ensuring each one will only appear once:

    declare -A users
    while IFS= read -r user
    do
      users["$user"]=''
    done < <(lslogins -r | awk -F '[ ]' 'NR > 1 && $6 { print $2 }')
    

    You may then iterate over them with the "${!array[@]}" syntax:

    for username in "${!users[@]}"
    do
      printf '%s\n' "$username"
    done
    

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.