LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (https://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   simple script (https://www.linuxquestions.org/questions/linux-newbie-8/simple-script-4175430562/)

karentc 10-04-2012 04:44 PM

simple script
 
I've used basic linux for a while, really basic. Now I need to write a simple script that will list all my users password status to a file.

I have a list of users (userlist.txt) and need to read that file and do chage -l user.

If you could tell me the steps are doing, it would help me to try other scripts. I'm using bash shell.

Thanks, Karen

porphyry5 10-04-2012 05:19 PM

Quote:

Originally Posted by karentc (Post 4797520)
I've used basic linux for a while, really basic. Now I need to write a simple script that will list all my users password status to a file.

I have a list of users (userlist.txt) and need to read that file and do chage -l user.

If you could tell me the steps are doing, it would help me to try other scripts. I'm using bash shell.

Thanks, Karen

If you enter 'chage' at the command line, it tells you this about its usage
Code:

~ $ chage
Usage: chage [options] [LOGIN]

Options:
  -d, --lastday LAST_DAY        set date of last password change to LAST_DAY
  -E, --expiredate EXPIRE_DATE  set account expiration date to EXPIRE_DATE
  -h, --help                    display this help message and exit
  -I, --inactive INACTIVE      set password inactive after expiration
                                to INACTIVE
  -l, --list                    show account aging information
  -m, --mindays MIN_DAYS        set minimum number of days before password
                                change to MIN_DAYS
  -M, --maxdays MAX_DAYS        set maximim number of days before password
                                change to MAX_DAYS
  -W, --warndays WARN_DAYS      set expiration warning days to WARN_DAYS

~ $

To do what you ask wouldn't need a formal script, just this command
Code:

chage -l < '/path/to/userlist.txt'
where 'chage -l' shows account aging information
< means following string is your input file path
Quotes on the file path are necessary only if it includes embedded whitespace (spaces, tabs, newlines), and may be single or double in this case
/path/to/ is the path to the directory containing the file of user names, not needed if that is the current directory
And it would show out put like this
Code:

~ $ chage -l g     
Last password change                                    : Jun 04, 2012
Password expires                                        : never
Password inactive                                      : never
Account expires                                        : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires      : 7
~ $

for each user name in userlist.txt (g is my user name)
To send the output to a file instead of printing it on the console, instead use the command as
Code:

chage -l < '/path/to/userlist.txt' > '/path/to/chageoutput.txt'

karentc 10-04-2012 05:35 PM

I did as you suggested. But received the following results. Maybe I'm missing something?
# chage -l < '/tmp/userlist.txt > '/tmp/output.txt'
>
>
>

suicidaleggroll 10-04-2012 05:37 PM

You're missing a single quote after userlist.txt

The > prompt is the shell waiting for you to finish the command. It has only encountered three quotes so far, it's expecting a fourth. You can use Ctrl+C to cancel the command, then you can fix it and run it again.

karentc 10-04-2012 05:41 PM

Sorry, missed that, but this is what I had when I first tried feeding the file in and then outputting.

chage -l < '/users/userlist.txt' > '/tmp/results.txt'
Usage: chage [-l] [-m min_days] [-M max_days] [-W warn]
[-I inactive] [-E expire] [-d last_day] user
#

suicidaleggroll 10-04-2012 05:45 PM

Yeah it looks like chage doesn't like that syntax. Try this
Code:

while read user; do echo $user; chage -l $user; done < /users/userlist.txt > /tmp/results.txt
Or in script form:
Code:

while read user; do
  echo $user
  chage -l $user
done < /users/userlist.txt > /tmp/results.txt


karentc 10-04-2012 05:57 PM

This is giving the results that prompted me to post this question. But I do appreciate the help. The data in the results.txt file is the same listing that is in the userlist.txt. So it's just outputting the file it read.

# while read user; do echo $user; chage -l $user; done < /users/userlist.txt > /tmp/results.txt
chage: unknown user: abennet
chage: unknown user: gwhite
chage: unknown user: jallan
chage: unknown user: csmith
#

suicidaleggroll 10-04-2012 06:13 PM

It's just outputting the same file it read because apparently none of the users exist on the system, so all you get is the "echo $user" with blank output from the chage -l.

This is how the output should look, if the users actually exist:
Code:

# cat users.txt
user1
user2
user3
# while read user; do echo $user; chage -l $user; done < users.txt > results.txt
# cat results.txt
user1
Last password change                                        : Mar 01, 2010
Password expires                                        : never
Password inactive                                        : never
Account expires                                                : never
Minimum number of days between password change                : 0
Maximum number of days between password change                : 99999
Number of days of warning before password expires        : 7
user2
Last password change                                        : Jul 02, 2012
Password expires                                        : never
Password inactive                                        : never
Account expires                                                : never
Minimum number of days between password change                : 0
Maximum number of days between password change                : 99999
Number of days of warning before password expires        : 7
user3
Last password change                                        : Apr 03, 2012
Password expires                                        : never
Password inactive                                        : never
Account expires                                                : never
Minimum number of days between password change                : 0
Maximum number of days between password change                : 99999
Number of days of warning before password expires        : 7
#


karentc 10-04-2012 06:25 PM

I must be doing something incorrectly. I have over 150 users, all exist in the /users/ directory. If I manually do chage -l user1, I get the correct output. But if user1 is in the list /users/userlist.txt, it doesn't output anything, except the name of the user. I've checked for spaces, and there aren't any. I could see one or two typos' in the list, but not all of them.

cbtshare 10-04-2012 08:09 PM

try

Quote:

#!/bin/bash

cat user.txt |
while read user;
do

echo Result For user $user Below >>output.txt
chage -l $user >> output.txt
done


suicidaleggroll 10-04-2012 09:33 PM

Quote:

Originally Posted by karentc (Post 4797593)
I must be doing something incorrectly. I have over 150 users, all exist in the /users/ directory. If I manually do chage -l user1, I get the correct output. But if user1 is in the list /users/userlist.txt, it doesn't output anything, except the name of the user. I've checked for spaces, and there aren't any. I could see one or two typos' in the list, but not all of them.

How did you make the list? Maybe it has some weird EOL terminators that are making their way into the chage -l call?

catkin 10-05-2012 02:38 AM

To see if the user names from the file are as expected, what is the output from
Code:

while read -r user; do echo ">$user<"; done < /users/userlist.txt

porphyry5 10-05-2012 10:12 AM

Try this approach
Code:

~ $ cat fred.txt
g
Root
q
~ $ mapfile < fred.txt x
~ $ y=${#x[@]}
~ $ for ((i=0; i<y; i++)); do chage -l ${x[$i]}; done
Last password change                                    : Jun 04, 2012
Password expires                                        : never
Password inactive                                      : never
Account expires                                        : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires      : 7
chage: user 'Root' does not exist in /etc/passwd
chage: user 'q' does not exist in /etc/passwd
~ $

mapfile is a bash internal that creates an array (x) from an input file (fred.txt) using an entire line as the content of each cell. Usually it is used with a '-t' option which omits the newline character from each line when building the array. Without '-t' each cell includes the newline, and I believe it was the lack of this which caused the failure of other methods.
y=${#x[@]} gets the number of cells in array x
The 'for' loop delivers the content of each cell in array x to chage, including the linebreak. That is, it simulates exactly what happens when you enter the username at the console, and press Return.

Mine is a single user machine, so /etc/passwd has only my user entry in it, hence the rejection of Root and q

Following is the 'for' loop rewritten to send chage's output to a file, o.txt. >> is used because we need each user output appended to the file
Code:

~ $ for ((i=0; i<y; i++)); do chage -l ${x[$i]} >> o.txt; done
chage: user 'Root' does not exist in /etc/passwd
chage: user 'q' does not exist in /etc/passwd
~ $ cat o.txt
Last password change                                    : Jun 04, 2012
Password expires                                        : never
Password inactive                                      : never
Account expires                                        : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires      : 7
~ $

Apparently the non-existent users are being rejected by bash and never presented to chage

And as chage doesn't bother to tell you the user name its dealing with, you might want to make the 'for' loop something like
Code:

for ((i=0; i<y; i++)); do chage -l ${x[$i]} >> o.txt; echo "Above Entry for User ${x[$i]}"; done

karentc 10-05-2012 10:13 AM

I copied and pasted your exact line and received
<abennet
<gwhite
<jallan
<csmith

It seems so simple, which is why I'm so stumped. I feel like I'm just missing some obvious typo.

karentc 10-05-2012 10:25 AM

Hmmm, it is a bash shell, but no mapfile found. I'm running Red Hat Enterprise Linux ES release 4 (Nahant Update 7)

While this hasn't solved my problem, I really appreciate the help and this has given me a lot more to work with. Thanks.

suicidaleggroll 10-05-2012 10:50 AM

Quote:

Originally Posted by karentc (Post 4798102)
I copied and pasted your exact line and received
<abennet
<gwhite
<jallan
<csmith

It seems so simple, which is why I'm so stumped. I feel like I'm just missing some obvious typo.

There's your problem, the formatting in your file is bad.

The output of that command should have looked like this:
Code:

>abennet<
>gwhite<
>jallan<
>csmith<

As I asked before, how did you make this list?

porphyry5 10-05-2012 11:06 AM

I don't know when mapfile was added to bash, I'm using this version
Code:

~/Documents $ bash --version
GNU bash, version 4.1.10(2)-release (i486-slackware-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
~/Documents $

Here's how to do it without mapfile
Code:

~ $ IFS=$'\x0A'
~ $ x=($(cat < fred.txt))
~ $ for ((i=0; i<y; i++)); do chage -l "${x[$i]}" >> cho.txt; done
chage: user 'Root' does not exist in /etc/passwd
chage: user 'q' does not exist in /etc/passwd
~ $ cat cho.txt
Last password change                                    : Jun 04, 2012
Password expires                                        : never
Password inactive                                      : never
Account expires                                        : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires      : 7
~ $ IFS=$'\x20'$'\x09'$'\x0A'     
~ $

IFS is bash's Input Field Separator, the value $'\x0A' makes it be only a newline, google ANSI-C quoting
x=($(cat < fred.txt)) creates the array x
you still need to set y as before to the number of items in x, but as I'm using the same terminal its still there for me.
IFS=$'\x20'$'\x09'$'\x0A' resets IFS to its default values, any one of space, tab or newline.

Bash is a delightfully arcane language. If you need a tutorial for it, try http://mywiki.wooledge.org/BashGuide or http://www.grymoire.com/Unix/Sh.html
The second one is a subset of bash, but as a single page it is easy to download and use offline, as it only references itself.

suicidaleggroll 10-05-2012 11:11 AM

I think these suggestions are getting needlessly complex. The problem is the formatting in his file, as evidenced by post #14. More intricate and complicated ways of calling chage which still depend on newline-separated fields in the file aren't going to help matters, because his file is apparently not newline-separated...at least not properly.

We need to go back to the source - how was the file created, and why does it not look like what any of these scripts expect? If that can be resolved, I think almost any of the solutions already posted in this thread will work fine.

Just my thoughts on the matter...

karentc 10-05-2012 01:19 PM

I tried:

for ((i=0; i<y; i++)); do chage -l ${x[$i]} >> test.txt; echo "Above Entry for User ${x[$i]}"; done

which produced nothing. Not even an output file. Again I appreciate your help.

porphyry5 10-05-2012 01:37 PM

Quote:

Originally Posted by karentc (Post 4798240)
I tried:

for ((i=0; i<y; i++)); do chage -l ${x[$i]} >> test.txt; echo "Above Entry for User ${x[$i]}"; done

which produced nothing. Not even an output file. Again I appreciate your help.

Then I think you should check the format of your input file, as others have suggested. A hex editor (bpe is a command-line hex editor often installed by default) will show you explicitly how each line is terminated. In a unix type file, each line should end with the hex character '0A', e.g.
Code:

~ $ bpe fred.txt

File [1 of 1]: fred.txt                                        Size: 0x00000009

COMMAND:                                              File position: 0x00000000

ADDRESS      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F  ASCII
-------------------------------------------------------------------------------
0x00000000  67 0A 52 6F 6F 74 0A 71 0A                        g.Root.q.

? in bpe accesses its help pages

karentc 10-05-2012 02:10 PM

so bpe doesn't exist on my system. The file was created from an ls -l > userlist.txt and then opened in excel, deleted what I didn't want and saved as a text file. Once transferred back, I checked it with vi.

porphyry5 10-05-2012 02:47 PM

Quote:

Originally Posted by karentc (Post 4798286)
so bpe doesn't exist on my system. The file was created from an ls -l > userlist.txt and then opened in excel, deleted what I didn't want and saved as a text file. Once transferred back, I checked it with vi.

vi will also work as a hex editor
Code:

:%!xxd        " enters hex mode
:%!xxd -r            " returns from hex mode


suicidaleggroll 10-05-2012 03:36 PM

Quote:

Originally Posted by karentc (Post 4798286)
so bpe doesn't exist on my system. The file was created from an ls -l > userlist.txt and then opened in excel, deleted what I didn't want and saved as a text file. Once transferred back, I checked it with vi.

Why all those steps? The excel part is almost certainly your problem. Try not to edit linux files in windows if at all possible, Windows likes to take over and mess everything up.

In the mean time, try running "dos2unix userlist.txt", and then re-run the test you did in step 14.

karentc 10-05-2012 03:55 PM

Thanks everyone. I found a post by Ricky_ds that came at the problem from a different angle and I ran that to an output file and I have everything I need. Again I appreciate all your time. Still not sure why this wouldn't work, but I have what I need. Just changed the location of the users and it's all good.

#!/bin/bash

formatDate() {
date +%d"."%m"."%Y -d " $1 day"
}
padr() {
string="$1................................................"
echo "$string" | cut -c1-$2
}
length() {
length=`echo "$@" | wc -c | cut -c1-8`
length=$(( $length -1 ))
echo $length
}
padl() {
string="................................................$1"
length=`length "$string"`
echo "$string" | cut -c`expr $length - $2`-$length
}

if [ "$#" = "0" ]; then
echo
echo "List of user accounts with password information"
echo "==============================================="
echo `date`
echo
echo "Legend: M=Minimum password age W=Warning in days before exp"
echo " Dis=Account Disabled"
echo "------------------------------------------------------------------------------------------"
echo "Username |Full name |UID |LastChange|M|Max |W|Passwd Exp|Dis |Acct Exp "
echo "----------|-------------------------|-----|----------|-|-----|-|----------|----|----------"
fi

sort /etc/passwd |grep ":/home/" | awk -F":" '{print}' |\
while read line ; do
name=`echo -e $line | awk -F: '{print$1}'`
uname=`cat /etc/shadow | grep -r "^$name:" | awk -F":" '{print}'`
thisuser=`echo -e $uname | awk -F: '{print$1}'`

if [ "$#" = "1" ] && [ "$1" = "$thisuser" ] || [ "$#" = "0" ]; then

a=`echo -e $uname | awk -F: '{print$3}'`
b=`echo -e $uname | awk -F: '{print$4}'`
c=`echo -e $uname | awk -F: '{print$5}'`
d=`echo -e $uname | awk -F: '{print$6}'`
e=`echo -e $uname | awk -F: '{print$7}'`
f=`echo -e $uname | awk -F: '{print$8}'`

uid=`echo -e $line | awk -F: '{print$3}'`
fullname=`echo -e $line | awk -F: '{print$5}'`

now=$(( ($(date +%s) / 86400) ))
pass=$(( $now - $a ))
last=`formatDate -$pass`

if test "$f" != "" ; then
next=$(( $f - $now ))
exp=`formatDate $next`
else
exp=`echo NEVER`
fi

pexpt=$(( $a + $c ))
pexpt=$(( $pexpt - $now ))
pexp=`formatDate $pexpt`

if [ "$#" = "1" ]; then
echo
echo "===Information for user $name==="
echo "Full name: $fullname"
echo "User ID: $uid"
echo "Password last changed: $last"
echo "Minumum password age: $b"
echo "Maximum password age: $c"
echo "Password warning age: $d"
echo "Password expires on: $pexp"
if test "$e" != "" ; then
echo "The account will be disabled *$e* days after expiration"
fi
echo "The account expires on: $exp"
echo
else
name=`padr $name 10`
c=`padl $c 4`
if test "$e" = "" ; then
e="."
fi
e=`padl $e 3`
exp=`padl $exp 9`
fullname=`padr "$fullname" 25`
uid=`padl $uid 4`
echo "$name|$fullname|$uid|$last|$b|$c|$d|$pexp|$e|$exp"
fi
fi

done

chrism01 10-07-2012 07:35 PM

As per suicidaleggroll; that was exactly my reaction as well


All times are GMT -5. The time now is 06:21 AM.