So I am stuck on trying to create a bash script while loop function that takes the input from /etc/passwd and outputs how many of users use bash, nolongin, csh, and ksh. I believe I am supposed to use cat and cut. And I know that the type of shell a user uses is on the 7th field.

Am I correct in thinking that I should create a variable for each type of shell and increment it each time it finds a user that uses that particular shell?

So far this is the code I have but for some reason it only outputs the final if statement..

declare -i bash;
declare -i nologin;
declare -i csh;
declare -i ksh;

cat /etc/passwd | cut -d: -f7 $line | while read line;
    if [ $line == "bash" ]
    else  fi [ $line == "/sbin/nologin" ]
    else fi [ $line == "chs" ]

Update: So far I have changed the code and updated what I have. It loops through and gives out the correct portion of the if statement, but I am having trouble trying to increment the variables I have.

  • You're off to a not-terrible start.  Read bash(1) to see how to do if-then-else blocks (although look also at case and think about whether that makes more sense for you).  Also look at how to do arithmetic with shell variables.  And read cut(1) to see how to get it to process data on the command line; and if you find a way, let me know, because I don't know of any (hint hint).  Finally, learn about UUoC. – Scott Apr 9 '15 at 3:08
  • first, I would replace fi by if in else clause – Archemar Apr 9 '15 at 7:45
  • What I did now is to pass cat /etc/passwd into cut using $line and if I do echo $line it spits out the data I need to compare in the if statements. The problem I have how to compare that $line with the specific thing I need. For example if the $line is "/bin/bash" It's supposed to increment the bash variable by 1. I took that stuff out for now to see which part of the if statements it chooses to go through and it always goes through the first if statement and then passes through that as if that was true. – DoctorWho22 Apr 9 '15 at 8:17

Here's my version:



while read line;
    innerline=$(echo "$line"| cut -d: -f7)

    if [[ $innerline == "/bin/bash" ]]
                    bash=$(echo "$bash+1"|bc);
    elif [[ $innerline == "/sbin/nologin" ]]
                    nologin=&(echo "$nologin+1"|bc);
    elif [[ $innerline == "/bin/csh" ]]
                    csh=$(echo "$csh+1"|bc);
                    ksh=$(echo "$ksh+1"|bc);
done < /etc/passwd

echo "$bash $nologin $csh $ksh"

To increment the values I pass the calculation job to bc which is small calculator utility.

Let me know if it works for you.

To be honest I must say I really dislike this solution. While loop is unnecessary and I prefer @Nat solution, however since you clearly stated that while loop must be present - here it is.

PS. This is just terrible idea:

                        ksh=$(echo "$ksh+1"|bc);

to assume that every unknown/unmatched shell is ksh.

| improve this answer | |
  • shouldn't you take into account that $line might hold /bin/bash not bash ? maybe $(basenamr $line) ? – Archemar Apr 9 '15 at 10:07
  • 1
    @Archemar, certainly! Also there are more shells - for example /bin/false. I'm not correcting the OP script in that matter as he might have some fake /etc/password on which he is working on. I'm just correcting the script operation, bash keywords (elif), redundant fi, double brackets, and providing the way how he can calculate the occurrences. – mnmnc Apr 9 '15 at 10:15
  • @Archemar, after consideration I've changed it since it should also benefit community not only OP. Although I think proper script should not have constant shell paths as matching patterns. The script should parse the /etc/passwd in first run to collect unique shells, then in second run it would calculate the occurrences. – mnmnc Apr 9 '15 at 10:20
  • ksh value will catch default case. – Archemar Apr 9 '15 at 10:25

I'm not sure if this is homework so this might not fit your requirements but you don't actually need a whole script to do this. This can be done with a one-liner.

cat /etc/passwd | cut -d ':' -f 7 | sort | uniq -c

Here's what the output looks like

  4 /bin/bash
  6 /sbin/nologin
  4 /usr/bin/csh
 13 /usr/bin/ksh

How this works is that uniq(1) "Filters adjacent matching lines from INPUT" and so with the -c option it will count adjacent lines which is why we need to sort the lines first so that all the matching lines will be adjacent.

| improve this answer | |
  • It is something I am working on for a Unix class so I am supposed to create a while loop function. – DoctorWho22 Apr 9 '15 at 7:54
  • I pretty much have it working I just need to find a way to increment the variables. – DoctorWho22 Apr 9 '15 at 8:42

Some tought

 cat /etc/passwd | while read line;

will place into line

 tmpfoo:x:1004:1005:utilisateur test sudo:/home/tmpfoo:/bin/bash


  cut -d: -f7 $line == "bash"

will evaluate to

 cut -d: -f7 tmpfoo:x:1004:1005:utilisateur test sudo:/home/tmpfoo:/bin/bash == "bash"

cut will search in the 3 files tmpfoo:x:1004:1005:utilisateur, test and sudo:/home/tmpfoo:/bin/bash which is probably not what you want.

as a gift, try this sample loop

cat /etc/passwd |  cut -d: -f7 | while read line;
    echo $line
| improve this answer | |

I believe I am supposed to use cat and cut.

It is something I am working on for a Unix class so I am supposed to create a while loop function.

Of course, files where the records are variable-length delimited by newlines and the fields are variable-length delimited by a single delimiter character, like the UNIX Version 7 password file, are things that awk excels at. So for the benefit of those people coming here without the aforegiven constraints, here's the awk one-liner:

awk -F : '{x[$7]++;} END {for (k in x) print k": "x[k]}' /etc/passwd

The lessons to learn here are:

  • UNIX has specialized tools tailored to its historical database file formats. This particular type of file format is a breeze to handle in awk.
  • You do not have to use cat, despite what instructors may say. ☺ There is such a thing as overuse of command pipelines. In the while loop case simple redirection will do, as in mnmnc's answer. In the case of awk, the files to process are simply given as arguments.
  • The while loop here is, of course, inside the awk program. Its primary function is to sequentially read and process all records in its input file(s), which it does with a loop.
  • Am I correct in thinking that I should create a variable for each type of shell and increment it each time it finds a user that uses that particular shell?
    Yes, and the awk one-liner here uses what is called an associative array, the x variable. The second part of the awk script shows the usual way to iterate over the contents of such an associative array. The first part is simply an increment (the ++ postfix operator) of the value of one element in the array, indexed by field seven of the record (which awk makes available as $7).

    Note that you can also do associative arrays in Bourne Again shell scripts and adapt this technique to the script for the constrained answer. You may be constrained to using a while loop; but no-one has constrained you to using if statements.

Further reading

| improve this answer | |
  • The problem is that the assignment given was to use cat and cut to read to determine which shell a particular user was using. – DoctorWho22 Apr 9 '15 at 15:56
  • That's addressed in the 2nd and 4th bullet points. – JdeBP Apr 9 '15 at 17:22

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.