The UNIX and Linux Forums

The UNIX and Linux Forums (http://www.unix.com/index.php)
-   UNIX for Dummies Questions & Answers (http://www.unix.com/unix-for-dummies-questions-and-answers/)
-   -   Single UNIX command to display users and to count them (http://www.unix.com/unix-for-dummies-questions-and-answers/208939-single-unix-command-display-users-count-them.html)

sreyan32 12-04-2012 06:17 AM

Single UNIX command to display users and to count them
 
Hello everyone,
I am new to Unix and I am stuck with a problem. I need only a single command to display the output of who and then add the total number of users and display at the bottom of that output.
Example-: (Expected output)
sreyan@debian:~$ <command>
sreyan tty7 2012-12-04 05:50 (:0)
sreyan pts/0 2012-12-04 05:51 (:0.0)
1

By the way I am using Debian 6.0.5 (64-bit). Thank You.

Skrynesaver 12-04-2012 06:53 AM

Something like this might do it
Code:

  w | tail -$(( $(w| wc -l) - 2 ))  | tee >(cut -d\  -f1|uniq | wc -l >/dev/stderr )

bakunin 12-04-2012 07:12 AM

Quote:

Originally Posted by sreyan32 (Post 302739373)
I need only a single command to display the output of who and then add the total number of users and display at the bottom of that output.

There is no such single command, because that would go against one of the most basic UNIX tenets: do one and do it as good as possible. That means: there is a command to display the users logged in ( who , as you have already figured out) and it will do this displaying as good as possible - but it won't count, sort or do any other "after-processing" of its display. This is simply not its job. There is a sort utility for sorting, but it will only do the sorting - it will not generate output of its own. The same goes for line counting (see the wc utility), filtering (see grep , sed , awk and a lot of similar tools) and any other purpose.

Think of the UNIX tools like instrumentalists in an orchestra. You don't expect the trumpet player to play the flute as well. No, you have a world-class flutist to play the flute and a world-class trumpet player to play the trumpet. It is your job to conduct them accordingly to bring out the brilliant and stunning sound they are capable to produce.

I hope this helps.

bakunin

sreyan32 12-04-2012 07:40 AM

Quote:

Originally Posted by Skrynesaver (Post 302739395)
Something like this might do it
Code:

  w | tail -$(( $(w| wc -l) - 2 ))  | tee >(cut -d\  -f1|uniq | wc -l >/dev/stderr )

Can you please break up this command and explain it to me. I am completely new to unix and don't understand it. But it has worked flawlessly.

bakunin 12-04-2012 09:02 AM

Quote:

Originally Posted by sreyan32 (Post 302739429)
Can you please break up this command and explain it to me. I am completely new to unix and don't understand it. But it has worked flawlessly.

Code:

w | tail -$(( $(w| wc -l) - 2 ))  | tee >(cut -d\  -f1|uniq | wc -l >/dev/stderr )
Lets start with the first part:

Code:

w | tail -$(( $(w| wc -l) - 2 ))
basically it executes "w" (see the man page of "w" for details) and pipes the output of it to "tail". You might want to read the man page of "tail" too, but in fact it displays a number of lines from some file or output stream - counted from the end. "tail -1" would display the last line, "tail -3" the last 3 lines, etc..

Instead of a fixed number like "1" or "3" an expression is used, which resolves to such a number:

Code:

$(( $(w| wc -l) - 2 ))
The "$(( ... ))" is just a device to make what comes out of the command inside part of the original line. Inside this there is another (numeric) expression: "<something> - 2". So we first have to examine what "something" does:

Code:

$(w| wc -l)
This calls again "w" and pipes its output to "wc". "wc" stands for "word count", with the "-l" parameter it counts lines instead of words. So we have:

Count the lines of "w"s output (innermost expression), then subtract 2 from it. This is the content of "$(( ... ))": the number of lines "w"s output has, minus 2.

At last we use this number as the parameter of "tail", so all lines from "w"s output, minus 2 lines, are printed. As "tail" (you remember?) outputs always the lastmost lines this

Code:

w | tail -$(( $(w| wc -l) - 2 ))
means: print all output of "w" save for the first two lines. This makes sense because the first two lines are header information and you donÄt want to use them.

Now lets see what is done with this output:

Code:

<w minus first 2 lines> | tee >(cut -d\  -f1|uniq | wc -l >/dev/stderr )
"tee" is a command you use to duplicate output streams. A UNIX command usually works like a garden hose: you put something in, it does doemthing with it inside, then something comes out. But once you direct this stream to some direction (like another command, a file, whatever) you can't use it anywhere else. This is what "tee" is for: it makes two (identical) streams out of one, so you can use both. The first stream is not processed at all so it is displayed as it is. Try it out (command up to "| tee ...") and you will recognize the part ot the output.

The other stream is directed at

Code:

<w minus first 2 lines> >(cut -d\  -f1|uniq | wc -l >/dev/stderr )
If first is directed at "cut", which cuts lines into "fields" (as always, i suggest you read the man page). In this case the delimiter character "blank" is selected "-d\ " (you have to precede special characters with "\") and "cut" is told we want only the first "field" (fields are parts of the line separated by the delimiter characters: "cut -d'|' -f3" would yield "3" of "a|b|3|4|x|y").

So, after mangling the output through "cut" it is reduced to the first word in the line, which is the users name. Next comes a command named "uniq", which filters out all the duplicates. If a user has several sessions open he would show up with several lines here and "uniq" takes care of that.

Finally, the so processed output is further processed by "wc -l", which i have explained above already - it yields the number of lines. This number is then displayed at "stderr", which means in this case it is displayed below the other output so far. Try the command

Code:

w | tail -$(( $(w| wc -l) - 2 ))  | cut -d\  -f1|uniq | wc -l
and you will see only this part of the output.

I hope this helps.

bakunin

sreyan32 12-04-2012 11:55 AM

Quote:

Originally Posted by bakunin (Post 302739481)

Code:

$(( $(w| wc -l) - 2 ))
bakunin

Thank You so much for your detailed explanation. But I have some doubts-:

1) In the following code-:

Code:

$(( $(w| wc -l) - 2 ))
Why have you used two opening and two closing brackets for the outer expression and only one opening and one closing for the inner expression that is-:

Code:

$(w| wc -l)
I tried it with two opening and closing brackets for both inner and outer expression and I keep getting errors. Same thing if I try with one bracket for both inner and outer expressions.Why does it only work like the way that you have specified ?

2) When we use tee the stream is duplicated and by default the first stream is always printed out. Am I right in assuming this ? Or is there a way to control the output of both the divided streams?

3)The duplicate stream that tee creates is also an output stream. And we redirect that second duplicate stream to the following operations-:

Code:

(cut -d\  -f1|uniq | wc -l >/dev/stderr )
But then if there is already an output stream then why are we redirecting the output to the error stream ?

By the way do you know what the following command does-:

Code:

kill $!
I searched for it in Google but I did not get any such answer. I just don't want to open up another thread for only such a small topic. If you do know the answer then please let me know.

Thank You again for your explanation. It did help a lot.

bakunin 12-04-2012 02:54 PM

Actually, these are very good questions and i am glad that you asked. It is always much more satisfying to teach someone willing to learn.

Quote:

Originally Posted by sreyan32 (Post 302739569)
1) In the following code-:

Code:

$(( $(w| wc -l) - 2 ))
Why have you used two opening and two closing brackets for the outer expression and only one opening and one closing for the inner expression that is-:

Code:

$(w| wc -l)
I tried it with two opening and closing brackets for both inner and outer expression and I keep getting errors. Same thing if I try with one bracket for both inner and outer expressions.Why does it only work like the way that you have specified ?

These are different language constructs. First the "$(( ... ))": this means: replace the numeric expression inside by its outcome. For instance you could write

Code:

foo -x $(( 5 - 3 + 1 ))
and this would be the same as

Code:

foo -x 3
The shell doesn't differentiate between numeric and string variables, at least not in the sense a high-level language does. Consider the following lines:

Code:

z=0
x="abc5"
x="${x#???}"    # this chops off the first 3 characters, leaving "5"
(( z = x * 5 ))
echo $z

I started the variable as string, then manipulated it like a string and finally, because its value was numeric, used it like an integer. So, if you want to make clear to the shell that the following is a numeric operation, you signify this by "(( ... ))". You can add "$" in front to replace it with its outcome in another line.

The other device, "$( ... )" is a subshell: it runs the commands inside in a separate shell and (the "$" in front) replaces it with its outcome (which could well be a string, it doesn't have to be numeric). It is the same device which is used at the end of the command:

Code:

>(cut -d\  -f1|uniq | wc -l >/dev/stderr )
Which means: run all the commands inside in a separate shell, directing something to the "stdin" input device of this shell (the ">").

Quote:

Originally Posted by sreyan32 (Post 302739569)
2) When we use tee the stream is duplicated and by default the first stream is always printed out. Am I right in assuming this ? Or is there a way to control the output of both the divided streams?

Yes, there is: the is the "-a" switch of "tee". I suggest you enter "man tee" to read its man page, which usually will also contain some examples.

Quote:

Originally Posted by sreyan32 (Post 302739569)
3)The duplicate stream that tee creates is also an output stream. And we redirect that second duplicate stream to the following operations-:

Code:

(cut -d\  -f1|uniq | wc -l >/dev/stderr )
But then if there is already an output stream then why are we redirecting the output to the error stream ?

First, the redirection to "stderr" is only happening with the very last commands output. Before this, it is always "stdout" to "stdin". In fact the pipe symbol "|" means: connect the "stdout" of the first process to "stdin" of the second process.

Whe i told you about UNIX processes being like garden hoses, this was a simplified picture of reality: in fact all the UNIX commands are more like "Y-shaped garden hoses". They have a "stdin" where data are puored in, but 2 outputs (one "stdout" for normal output, the other "stderr" usually used for error messages), which can both be directed anywhere separately. For instance:
connect the stdout
Code:

ls -l /etc/passwd /some/file/blabla
will produce an error message because "/some/file/blabla" doesn't exist. This error message is going through "stderr" and usually it lands on the screen because both "stdin" and "stderr" are both pointing to the same device per default. That in fact they are from different sources is easy to see once we redirect one of these sources away:

Code:

ls -l /etc/passwd /some/file/blabla > /dev/null
ls -l /etc/passwd /some/file/blabla 2> /dev/null

Notice the difference in output. "/dev/null" is simply the catch-all device. It works like a black hole: whatever goes in is lost, out comes nothing.

Now, to make sure both output streams are separated from each other and are displayed one after the other (not mixed up) it is a good idea to let the one output go to "stdout", the other to "stderr". There are also some intricate differences (buffered versus unbuffered output) i don't think you will grasp for now. I hope you understand that i forego the explanation of some very advanced stuff to a newbie. Still, it is impressing that you came up with this very good question.

Quote:

Originally Posted by sreyan32 (Post 302739569)
By the way do you know what the following command does-:
Code:

kill $!

First, "kill" is used to send signals to a process. "kill" is a regular command and has a man page i suggest you read. It takes a process number as an argument, to which it sends a signal. As there is no signal specified "kill" will send its default signal, which is "TERM" - it asks the process to close down. "$!" is a special variable maintained by the shell and holds the process ID of the last process started in background. So, this command terminates the last started background process.

I hope this helps.

bakunin


All times are GMT -4. The time now is 02:14 PM.

Linux and Unix Supported by: vBulletin
Search Engine Optimisation provided by DragonByte SEO v1.0.9 (Pro) - vBulletin Mods & Addons Copyright © 2014 DragonByte Technologies Ltd.
The UNIX and Linux Forums Content Copyright ©1993-2013. All Rights Reserved.
Forum Operations by The UNIX and Linux Forums