Linux usernames /etc/passwd listing

1.2k Views Asked by At

I want to print the longest and shortest username found in /etc/passwd. If I run the code below it works fine for the shortest (head -1), but doesn't run for (sort -n |tail -1 | awk '{print $2}). Can anyone help me figure out what's wrong?

#!/bin/bash

grep -Eo '^([^:]+)' /etc/passwd | 
while read NAME
do
  echo ${#NAME} ${NAME}
done |
sort -n |head -1 | awk '{print $2}' 
sort -n |tail -1 | awk '{print $2}'
5

There are 5 best solutions below

0
Jedi On BEST ANSWER

If you want both the head and the tail from the same input, you may want something like sed -e 1b -e '$!d' after you sort the data to get the top and bottom lines using sed.

So your script would be:

#!/bin/bash
grep -Eo '^([^:]+)' /etc/passwd | 
while read NAME
do
  echo ${#NAME} ${NAME}
done |
sort -n |  sed -e 1b -e '$!d'

Alternatively, a shorter way:

cut -d":" -f1 /etc/passwd | awk '{ print length, $0 }' | sort -n | cut -d" " -f2- | sed -e 1b -e '$!d'
0
Callie J On

The problem is that you only have two pipelines, when you really need one. So you have grep | while read do ... done | sort | head | awk and sort | tail | awk: the first sort has an input (i.e., the while loop) - the second sort doesn't. So the script is hanging because your second sort doesn't have an input: or rather it does, but it's STDIN.

There's various ways to resolve:

  • save the output of the while loop to a temporary file and use that as an input to both sort commands
  • repeat your while loop
  • use awk to do both the head and tail

The first two involve iterating over the password file twice, which may be okay - depends what you're ultimately trying to do. But using a small awk script, this can give you both the first and last line by way of the BEGIN and END blocks.

0
Sanjay Barnwal On

Here the issue is:

Piping finishes with the first sort -n |head -1 | awk '{print $2}' command. So, input to first command is provided through piping and output is obtained.

For the second command, no input is given. So, it waits for the input from STDIN which is the keyboard and you can feed the input through keyboard and press ctrl+D to obtain output.

Please run the code like below to get desired output:

#!/bin/bash

    grep -Eo '^([^:]+)' /etc/passwd | 
    while read NAME
    do
      echo ${#NAME} ${NAME}
    done |
    sort -n |head -1 | awk '{print $2}' 

    grep -Eo '^([^:]+)' /etc/passwd | 
    while read NAME
    do
      echo ${#NAME} ${NAME}
    done |
    sort -n |tail -1 | awk '{print $2}

'

2
Ed Morton On

All you need is:

$ awk -F: '
    NR==1 { min=max=$1 }
    length($1) > length(max) { max=$1 }
    length($1) < length(min) { min=$1 }
    END { print min ORS max }
' /etc/passwd

No explicit loops or pipelines or multiple commands required.

0
David C. Rankin On

While you already have good answers, you can also use POSIX shell to accomplish your goal without any pipe at all using the parameter expansion and string length provided by the shell itself (see: POSIX shell specifiction). For example you could do the following:

#!/bin/sh

sl=32;ll=0;sn=;ln=;         ## short len, long len, short name, long name
while read -r line; do      ## read each line
    u=${line%%:*}           ## get user
    len=${#u}               ## get length
    [ "$len" -lt "$sl" ] && { sl="$len"; sn="$u"; } ## if shorter, save len, name
    [ "$len" -gt "$ll" ] && { ll="$len"; ln="$u"; } ## if longer, save len, name
done </etc/passwd
printf "shortest (%2d): %s\nlongest  (%2d): %s\n" $sl "$sn" $ll "$ln"

Example Use/Output

$ sh cketcpw.sh
shortest ( 2): at
longest  (17): systemd-bus-proxy

Using either pipe/head/tail/awk or the shell itself is fine. It's good to have alternatives.

(note: if you have multiple users of the same length, this just picks the first, you can use a temp file if you want to save all names and use -le and -ge for the comparison.)