0

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;

bash=0;
nologin=0;
csh=0;
ksh=0;
cat /etc/passwd | cut -d: -f7 $line | while read line;
do
    if [ $line == "bash" ]
            then
                    bash=bash+1;
    else  fi [ $line == "/sbin/nologin" ]
            then
                    nologin=nologin+1;
    else fi [ $line == "chs" ]
            then
                    chs=chs+1;
    else
            then
                    ksh=ksh+1;
    fi
done

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.

3
  • 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. Apr 9, 2015 at 3:08
  • first, I would replace fi by if in else clause
    – Archemar
    Apr 9, 2015 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. Apr 9, 2015 at 8:17

4 Answers 4

1

Here's my version:

#!/bin/bash

bash=0;
nologin=0;
csh=0;
ksh=0;

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

    if [[ $innerline == "/bin/bash" ]]
    then
                    bash=$(echo "$bash+1"|bc);
    elif [[ $innerline == "/sbin/nologin" ]]
    then
                    nologin=&(echo "$nologin+1"|bc);
    elif [[ $innerline == "/bin/csh" ]]
    then
                    csh=$(echo "$csh+1"|bc);
    else
                    ksh=$(echo "$ksh+1"|bc);
    fi
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:

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

to assume that every unknown/unmatched shell is ksh.

4
  • shouldn't you take into account that $line might hold /bin/bash not bash ? maybe $(basenamr $line) ?
    – Archemar
    Apr 9, 2015 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, 2015 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, 2015 at 10:20
  • ksh value will catch default case.
    – Archemar
    Apr 9, 2015 at 10:25
3

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.

2
  • It is something I am working on for a Unix class so I am supposed to create a while loop function. Apr 9, 2015 at 7:54
  • I pretty much have it working I just need to find a way to increment the variables. Apr 9, 2015 at 8:42
0

Some tought

 cat /etc/passwd | while read line;

will place into line

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

then,

  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;
do
    echo $line
done
0

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

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .