Print unique names of users logged on with finger - bash

I'm trying to write a shell script that prints the full names of users logged on to a machine. The finger command gives me a list of users, but there are many duplicates. How can I loop through and print out only the unique ones?
Edit:
This is the format of what finger gives me:
xxxx XX of group XXX pts/59 1:00 Feb 13 16:38
xxxx XX of group XXX pts/71 1:11 Feb 13 16:27
xxxx XX of group XXX pts/105 1d Feb 12 15:22
xxxx YY of group YYY pts/102 2:19 Feb 13 14:13
xxxx ZZ of group ZZZ pts/42 2d Feb 7 12:11
I'm trying to extract the full name (i.e. whatever comes before 'of group' in column 2), so I would be using awk together with finger.

What you want is actually fairly difficult in a shell script, here is, for example, my full output of finger(1):
Login Name TTY Idle Login Time Office Phone
martin Martin Tournoij *v0 1d Wed 14:11
martin Martin Tournoij pts/2 22 Wed 15:37
martin Martin Tournoij pts/5 41 Thu 23:16
martin Martin Tournoij pts/7 31 Thu 23:24
martin Martin Tournoij pts/8 Thu 23:29
You want the full name, but this may contain 1 space (as per my example), or it may just be 'Teller' (no space), or it may be 'Captain James T. Kirk' (3 spaces). So you can't just use the space as delimiter. You could use the character position of 'TTY' in the header as an indicator, but that's not very elegant IMHO (especially with shell scripting).
My solution is therefore slightly different, we get only the username from finger(1), then we get the full name from /etc/passwd
#!/bin/sh
prev=""
for u in $(finger | tail +2 | cut -w -f1 | sort); do
[ "$u" = "$prev" ] && continue
echo "$u $(grep "^$u" /etc/passwd | cut -d: -f5)"
prev="$u"
done
Which gives me both the username & login name:
martin Martin Tournoij
Obviously, you can also print just the real name (without the $u).

The sort and uniq BinUtils commands can be used to removed duplicates.
finger | sort -u
This will remove all duplicate lines, but you will still see similar lines due to how verbose the finger command is. If you just want a list of usernames, you can filter it out further to be very specific.
finger | cut -d ' ' -f1 | sort -u
Now, you can take this one step further, and remove the "header/label" line printed out by the finger command.
finger | cut -d ' ' -f1 | sort -u | grep -iv login
Hope this helps.

Other possible solution:
finger | tail -n +2 | awk '{ print $1 }' | sort | uniq
tail -n +2 to omit the first line.
awk '{ print $1 }' to extract the first column.
sort to prepare input for uniq.
uniq remove duplicates.
If you want to iterate use:
for user in $(finger | tail -n +2 | awk '{ print $1 }' | sort | uniq)
do
echo "$user"
done

Could this be simpler?
No spaces or any other special characters to worry about!
finger -l | awk '/^Login/'
Edit: To remove the content after of group
finger -l | awk '/^Login/' | sed 's/of group.*//g'
Output:
Login: xx Name: XX
Login: yy Name: YY
Login: zz Name: ZZ

Related

Awk, or similar command, to get the last column and do some action with it in Bash

I'm writing a script to grab the last update/patch date on a few hundred servers. Lacking another tool due to various reasons, I've decided to write a script. At the moment I'm using the following command:
sudo yum history | grep [0-9] | grep -E 'Update|Install|U|I' | awk 'NR==1'
Which gives me the first line with an action on it. But it only gives me the first line, I toyed with the idea of grabbing the first 5 rows but that may not be applicable to every situation.
sudo yum history | grep [0-9] | grep -E 'Update|Install|U|I' | awk 'NR>=1&&NR<=5'
So I would like to check the last column or two and if more than x packages have been updated or installed then to grab that row.
Generically speaking the output of yum history is:
18 | first last <username> | 2018-08-30 19:41 | E, I, U | 43 ss
17 | first last <username> | 2018-07-10 15:28 | E, I, U | 230 EE
16 | first last <username> | 2018-04-25 20:08 | E, I, U | 44 ss
15 | first last <username> | 2018-01-30 20:57 | E, I, O, U | 108 EE
14 | first last <username> | 2018-01-30 20:39 | Install | 4
The issue I'm running into is the last two columns can differ in their column position and the last column may just be numeric or it may contain letters or special characters. I want to ignore any last column that has any character that is not numeric, and to evaluate whether the last column has more than 20 packages installed or updated. If the last column is more than 20 packages to then grab that row and only that row.
Use a regular expression, matching for the number in the last column. To print all history records with >=20 alterations:
sudo yum history | \
perl -ne '/\| *(\d+)[^\|]*$/ and $1>=20 and print($_)'
Of - if you only want the time stamp from matching history records:
sudo yum history | \
perl -ne '
#col=split(/\|/);
$col[4]=~/^ *(\d+)/ and $1>=20 and print($col[2],"\n")'
use awk:
sudo yum history | awk -F ' *\\| *' '$4 ~ /\<(Install|Update|U|I)\>/ && $5 > 20'

Csh - Fetching fields via awk inside xargs

I'm struggling to understand this behavior:
Script behavior: read a file (containing dates); print a list of files in a multi-level directory tree and get their size, print the file size only, (future step: sum the overall file size).
Starting script:
cat dates | xargs -I {} sh -c "echo '{}: '; du -d 2 "/folder/" | grep {} | head"
2000-03:
1000 /folder/2000-03balbasldas
2000-04:
12300 /folder/2000-04asdwqdas
[and so on]
But when I try to filter via awk on the first field, I still get the whole line
cat dates | xargs -I {} sh -c "echo '{}: '; du -d 2 "/folder/" | grep {} | awk '{print $1}'"
2000-03:
1000 /folder/2000-03balbasldas
2000-04:
12300 /folder/2000-04asdwqdas
I've already approached it via divide-et-impera, and the following command works just fine:
du -d 2 "/folder/" | grep '2000-03' | awk '{print $1}'
1000
I'm afraid that I'm missing something very trivial, but I haven't found anything so far.
Any idea? Thanks!
Input: directory containing folders named YYYY-MM-random_data and a file containing strings:
ls -l
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-03-blablabla
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-04-blablabla
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-05-blablabla
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-06-blablabla
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-06-blablablb
drwxr-xr-x 2 user staff 68 Apr 24 11:21 2000-06-blablablc
[...]
cat dates
2000-03
2000-04
2000-05
[...]
Expected output: sum of the disk space occupied by all the files contained in the folder whose name include the string in the file dates
2000-03: 1000
2000-04: 2123
2000-05: 1222112
[...]
======
But in particular, I'm interested in why awk is not able to fetch the column $1 I asked it to.
Ok it seems I found the answer myself after a lot of research :D
I'll post it here, hoping that it will help somebody else out.
https://unix.stackexchange.com/questions/282503/right-syntax-for-awk-usage-in-combination-with-other-command-inside-xargs-sh-c
The trick was to escape the $ sign.
cat dates | xargs -I {} sh -c "echo '{}: '; du -d 2 "/folder/" | grep {} | awk '{print \$1}'"
Using GNU Parallel it looks like this:
parallel --tag "eval du -s folder/{}* | perl -ne '"'$s+=$_ ; END {print "$s\n"}'"'" :::: dates
--tag prepends the line with the date.
{} is replaced with the date.
eval du -s folder/{}* finds all the dirs starting with the date and gives the total du from those dirs.
perl -ne '$s+=$_ ; END {print "$s\n"}' sums up the output from du
Finally there is bit of quoting trickery to get it quoted correctly.

Bash Substring multiple parameters

I need to extract two parameters from each line of a svn log but I am not able to do it with grep.
My Svn log command, such as
svn log http://svn.apache.org/repos/asf/xerces/java/trunk/ | grep "^r[0-9]\+ | " | cut -c2-
Outputs the result in this format:
318150 | lehors | 2002-01-28 20:48:11 +0100 (Mon, 28 Jan 2002) | 2 lines
318149 | elena | 2002-01-28 20:46:33 +0100 (Mon, 28 Jan 2002) | 12 lines
318148 | lehors | 2002-01-28 20:33:36 +0100 (Mon, 28 Jan 2002) | 2 lines
318147 | lehors | 2002-01-28 20:22:51 +0100 (Mon, 28 Jan 2002) | 2 lines
How can I grep the release number (first parameter) and the date in this format?
318150 2002-01-28
318149 2002-01-28
318148 2002-01-28
318147 2002-01-28
Use a more robust Awk for this to pattern-match/extract from individual columns.
.. | awk 'BEGIN{FS="|"}{split($3,temp, " "); print $1,temp[1]}'
318150 2002-01-28
318149 2002-01-28
318148 2002-01-28
318147 2002-01-28
The .. | part represents the command to be included that produces the required output which is pipe-lined to Awk
The logic is pretty straight-forward, split input lines with de-limiter as | which is done by FS="|". Now $1 represents the first field you want, and for the second part, split the part $3 and use the split() function to split on delimiter, a white-space character and store it in array temp, so that it can be accessed as temp[1], the other space fields are present in the array from the next index on wards.
So ideally I guess it should be,
svn log http://svn.apache.org/repos/asf/xerces/java/trunk/ | \
awk 'BEGIN{FS="|"}{split($3,temp, " "); print $1,temp[2]}'
Alternatively you could use GNU grep with its -E extended regular expression capabilities, but it is just not good enough to show the matching entries on same line like,
grep -oE '[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}' file
(and)
grep -oE '^[[:digit:]]{6}' file
but not together as I have used the -o flag to print the match only part.
As your file is separated by a single space and you want to have the first and 5th columns, this is another solution by using cut:
cut -d' ' -f1,5 < svn_log_output_file
(or piping cut -d' ' -f1,5 to your command)
A much simpler approach with multiple delimiters-
awk -F '[| ]' '{print $1, $7}' file
Where file contains the output you showed in your question.
Output-
318150 2002-01-28
318149 2002-01-28
318148 2002-01-28
318147 2002-01-28
Of course, you don't need to store in an intermediate file. You can do-
svn log http://svn.apache.org/repos/asf/xerces/java/trunk/ \
| grep "^r[0-9]\+ | " | cut -c2- | \
awk -F '[| ]' '{print $1, $7}'
awk '{print $1,$5}' file
318150 2002-01-28
318149 2002-01-28
318148 2002-01-28
318147 2002-01-28

Script to generate a list to run a command

Sorry for the semi-vague title, I wasn't exactly sure how to word it. I'm looking to generate a list, excluding devices without a matching major/minor number, and run
lkdev -l hdiskn -a -c DATAn
where the hdisk and the DATA device having corresponding major/minor numbers.
In /dev, I have -
root# testbox /dev
#ls -l | grep -E "DATA|hdisk" | grep -v rhd
crw-r--r-- 1 root system 18, 3 Oct 03 10:50 DATA01
crw-r--r-- 1 root system 18, 2 Oct 03 10:50 DATA02
brw------- 1 root system 18, 1 Apr 12 2013 hdisk0
brw------- 1 root system 18, 0 Apr 12 2013 hdisk1
brw------- 1 root system 18, 3 Jan 14 2014 hdisk2
brw------- 1 root system 18, 2 Jan 14 2014 hdisk3
brw------- 1 root system 18, 4 Jan 14 2014 hdisk4
So essentially, I'm trying to create something where hdisk0,1,4 are all excluded, and hdisk2-3 are locked with DATA01 and DATA02, respectively.
I originally was trying to use sort and/or uniq to isolate/remove fields, but haven't been able to generate the desired list to even begin looking at running the command on each.
(As a note, I have several servers with hundreds of these. If it were just these few, I'd find a "simpler" way.)
(I can't test it right now, so please correct syntax errors if any)
You could play with sort en uniq like beneath
ls -l | grep -E "DATA|hdisk" | sed -e 's/.* \([0-9]*, *[0-9]*\).*/\1/' | sort |
uniq -c | grep -v " 1" | cut -c8- | while read majorminor; do
ls -l | grep " ${majorminor}" | sed 's/.* //'
done
However, you should start with selecting the right lines without counting:
for data in $(find /dev -type c -name "DATA*" | cut -d/ -f3); do
majorminor="$(ls -l $data | sed -e 's/.* \([0-9]*, *[0-9]*\).*/\1/')"
echo "$data <==> $(ls -l hdisk* | grep " ${majorminor}" | sed 's/.* //')"
done

Find most frequent line in file in bash

Suppose I have a file similar to as follows:
Abigail 85
Kaylee 25
Kaylee 25
kaylee
Brooklyn
Kaylee 25
kaylee 25
I would like to find the most repeated line, the output must be just the line.
I've tried
sort list | uniq -c
but I need clean output, just the most repeated line (in this example Kaylee 25).
Kaizen ~
$ sort zlist | uniq -c | sort -r | head -1| xargs | cut -d" " -f2-
Kaylee 25
does this help ?
IMHO, none of these answers will sort the results correctly. The reason is that sort, without the -n, option will sort like this "1 10 11 2 3 4", etc., instead of "1 2 3 4 10 11 12". So, add -n like so:
sort zlist | uniq -c | sort -n -r | head -1
You can then, of course, pipe that to either xargs or sed as described earlier.
awk -
awk '{a[$0]++; if(m<a[$0]){ m=a[$0];s[m]=$0}} END{print s[m]}' t.lis
$ uniq -c list | sort -r | head -1 | awk '{$1=""}1'
Kaylee 25
Is this what you're looking for?

Resources