1

I was trying to understand the usage of IFS but there is something I couldn't find any information about.

My example code:

#!/bin/sh
# (C) 2016 Ergin Bilgin

IFS=!
for LINE in $(last -a | sed '$ d')
do
    echo $LINE | awk '{print $1}'
done
unset IFS

I use this code to print last users line by line. I totally understand the usage of IFS and in this example when I use default IFS, it reads word by word inside of my loop. And when I use IFS=! it reads line by line as I wish. The problem here is I couldn't find anything about that "!" on anywhere. I don't remember where I learned that. When I google about achieving same kind of behaviour, I see other values which are usually strings.

So, what is the meaning of that "!" and how it gives me the result I wish?

Thanks

banderson
  • 65
  • 1
  • 11

2 Answers2

2

IFS=! is merely setting a non-existent value for IFS so that you can iterate input line by line. Having said that using for loop here is not recommended, better to use read in a while loop like this to print first column i.e. username:

last | sed '$ d' | while read -r u _; do
   echo "$u"
done
anubhava
  • 761,203
  • 64
  • 569
  • 643
1

As you are aware, if the output of last had a !, the script would split the input lines on that character.

The output format of last is not standardized (not in POSIX for instance), but you are unlikely to find a system where the first column contains anything but the name of whatever initiated an action. For instance, I see this:

tom      pts/8        Wed Apr 27 04:25   still logged in    michener.jexium-island.net
tom      pts/0        Wed Apr 27 04:15   still logged in    michener.jexium-island.net
reboot   system boot  Wed Apr 27 04:02 - 04:35  (00:33)     3.2.0-4-amd64
tom      pts/0        Tue Apr 26 16:23 - down   (04:56)     michener.jexium-island.net

continuing to

reboot   system boot  Fri Apr  1 15:54 - 19:03  (03:09)     3.2.0-4-amd64
tom      pts/0        Fri Apr  1 04:34 - down   (00:54)     michener.jexium-island.net

wtmp begins Fri Apr  1 04:34:26 2016

with Linux, and different date-formats, origination, etc., on other machines.

By setting IFS=!, the script sets the field-separator to a value which is unlikely to occur in the output of last, so each line is read into LINE without splitting it. Normally, lines are split on spaces.

However, as you see, the output of last normally uses spaces for separating columns, and it is fed into awk which splits the line anyway — with spaces. The script could be simplified in various ways, e.g.,:

#!/bin/sh

for LINE in $(last -a | sed -e '$ d' -e 's/ .*//')
do    
    echo $LINE
done

which is (starting from the example in the question) adequate if the number of logins is not large enough to exceed your command-line. While checking for variations in last output, I noticed one machine with about 9800 lines from several years. (The other usual motivations given for not using for-loops are implausible in this instance). As a pipe:

#!/bin/sh

last -a | sed -e 's/ .*//' -e '/^$/d' | while IFS= read LINE
do
    echo $LINE
done

I changed the sed expression (which OP likely copied from some place such as Bash - remove the last line from a file) because it does not work.

Finally, using the -a option of last is unnecessary, since all of the additional information it provides is discarded.

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • [Why you don't read lines with "for"](http://mywiki.wooledge.org/DontReadLinesWithFor) and [BashFAQ/001](http://mywiki.wooledge.org/BashFAQ/001) – anubhava Apr 27 '16 at 14:42