Fine, I'll make it more points...if that'll make people happier; I'd do it myself if I knew how to program.
Main Topics
Browse All TopicsI need a shell script to run as root, it will search all the users who have MP3s, list there usernames, list the MP3s, and delete them if I so desire. So, what I am looking for is as follows. A program that searches, lists each user with a number before their username, which then the program will let me choose which user by number (or name), list all the MP3s that the user has and asks me if I want to delete them. This could also be a C program. I need this by Monday at the earliest, and a week Monday at the latest.
This Question has been solved and asker verified All Experts Exchange premium technology solutions are available to subscription members.
Experts Exchange has been collecting answers to technology questions since 1996…3 million and counting! If you have a question, chances are we already have your answer.
If you can't find the exact answer you're looking for, ask our exclusive community of 50,000 experts. You’ll get a personalized answer from a trusted professional.
Thousands of free tech tips, tricks, how-to’s and tutorials are available in our peer reviewed articles section. See for yourself how smart our experts are, no login required.
Access the answers to your technology questions today.
30-day free trial. Register in 60 seconds.
Members of the expert community talk about why the experience at Experts Exchange is different than what you will find anywhere else.

Try it out and discover for yourself.
30-day free trial. Register in 60 seconds.
Join the community of experts here and help other tech pros by answering question in your area of expertise. You can earn FREE access to all Experts Exchange's premium features and resources.
#!/usr/local/bin/perl
use warnings;
use File::Find ();
$cols = 3;
$linewidth = 80; # Columns on terminal
$lines = 24; # Lines on terminal
$| = 1;
# Get a list of users:
@users = ();
setpwent();
while ($user = getpwent()) {
push(@users, $user);
}
endpwent();
$width = int($linewidth/$cols) - 4;
$col = 0;
$line = 0;
foreach $i (1..@users) {
printf "%3i %-${width}s", $i, $users[$i-1];
$col = ($col + 1) % $cols;
if (!$col) {
print "\n";
$line = ($line + 1) % ($lines - 1);
if (!$line) {
print "Press RETURN to continue...";
$ans = <STDIN>;
}
}
}
print "\n" if ($col);
print "Enter user number or userid? ";
chomp($user = <STDIN>);
$user = $users[$user] if ($user =~ /^\d+$/);
$dir = (getpwnam($user))[7];
die "Invalid user $user\n" unless (defined($dir));
chdir($dir) or die "Can't chdir `$dir': $!\n";
@mp3 = ();
$line = 0;
File::Find::find({wanted => \&wanted}, '.');
$n = @mp3;
if ($n == 0) {
print "No mp3 files found\n";
exit(0);
} elsif ($n == 1) {
print "Delete this file (y/n)? ";
} else {
print "Delete these $n files (y/n)? ";
}
chomp($ans = <STDIN>);
if ($ans =~ /^y/i) {
$done = unlink(@mp3);
print "$done file(s) deleted\n";
}
sub wanted {
if (/^.*\.mp3\z/is) {
push(@mp3, $File::Find::name);
print("$File::Find::name\n
$line = ($line + 1) % ($lines - 1);
if (!$line) {
print "Press RETURN to continue...";
$ans = <STDIN>;
}
}
}
here is my version:
#!/usr/bin/perl -w
#
# This program searches for mp3's and in a draconing way, zaps them.
#
# William Julien
####
use strict;
use File::Find;
#
# put perl into interactive mode
#
BEGIN {
$| = 0;
}
#
# declare our variables
#
my ($userid, # userid
$uid, # numerid uid
$username, # user name
$prompt, # prompt variable
$gecos, # contents of the gecos field
%userdata, # user information
@list, # list of all mp3 files
%filelist # list of mp3 files for a user
);
#
# get the directory by parameter or default to /home
#
@ARGV or push @ARGV, '/home';
#
# first get a list of userid's and user names on the system
#
open PASSWD, "/etc/passwd";
while (<PASSWD>) {
($userid, $uid, $gecos) = (split /:/)[0,2,4];
$username = (split /,/,$gecos)[0];
$userdata{$uid} = [ $userid, $username ];
}
close PASSWD;
#
# find all the mp3s
#
find (\&wanted, @ARGV);
#
# associate each filename with a user
#
foreach (@::list) {
$uid = (stat("$_"))[4];
if ( $uid > 0 ) {
push @{$filelist{$uid}}, $_;
}
}
#
# prompt for a uid
#
while(1) {
#
# display a list of userid's with mp3s
#
if ( scalar keys %filelist > 0 ) {
print "The following user(s) have mp3 files\n\n";
printf "%6s %8s %s\n", "uid","userid","username";
foreach $uid ( keys %filelist ) {
printf "%6d %8s %s\n", $uid,
${userdata{$uid}}[0],
${userdata{$uid}}[1];
}
} else {
print "No users have mp3 files\n";
exit;
}
print "\nEnter the uid to see the list of files (0 to exit)-> ";
$prompt = <STDIN>;
chomp($prompt);
$uid = $prompt;
if ( $prompt eq "0" ) {
print "Done\n";
exit;
}
if ( defined $filelist{$uid} ) {
foreach (@{$filelist{$uid}}) {
print "$_\n";
}
print "\nWould you like to zap them? (yes or no) -> ";
$prompt = <STDIN>;
chomp($prompt);
if ( $prompt eq "yes" ) {
foreach (@{$filelist{$uid}}) {
print "Zapping $_\n";
unlink $_;
}
delete $filelist{$uid};
} else {
print "Ok... The files are not zapped\n";
}
} else {
print "Sorry, not a valid uid\n";
}
}
# fin
###
#
# subroutine to find mp3 files
#
sub wanted {
my $uid;
if ( /mp3$/ ) {
push @::list, $File::Find::name;
}
}
Comments on martin's version:
your version doesn't actually find users that have mp3 files (it lists all users) and fails to work for those that do.
Did you test it?
-->touch junk.mp3
-->./martin.pl
1 root 2 bin 3 daemon
4 adm 5 lp 6 sync
7 shutdown 8 halt 9 mail
10 news 11 uucp 12 operator
13 games 14 ftp 15 gdm
16 nobody 17 moonbeam 18 informix
19 oracle 20 mandoman 21 backup
Enter user number or userid? 17
Can't opendir(.): Permission denied
No mp3 files found
-->ls -l *mp3
-rw-r----- 1 moonbeam kittens 0 Sep 18 11:45 junk.mp3
Regarding your comments.
1. true, but easily fixed by opening a process to a niscat passwd instead of the /etc/passwd.
open(PASSWD, "niscat passwd|");
2. true. I missed the "\i". change ...
if(/mp3$/) to if(/mp3$/i)
ps.. I assumed that CyberMage would want to restrict the search to a particular user, which is why I coded...
@ARGV or push @ARGV, '/home';
....
find (\&wanted, @ARGV);
The trouble with your routine is that for all the user's listed, which one has the targeted files? Imagine a system with 500 or more or so userids. It would take all afternoon to try them all... one at a time. Very tedious. I say, let the computer do the work.
By the way, I posted to linux/General an updated version with a simplified user interface. It works like this:
-->touch fred.mP3
-->zap_mp3.pl $HOME
The following user(s) have mp3 files
uid userid username
502 moonbeam William Julien
Enter the uid to see the list of files (0 to exit)-> 502
/home/moonbeam/fred.mP3
Would you like to zap them? (yes or no) -> yes
Zapping /home/moonbeam/fred.mP3
No users have mp3 files
Here is the source.
#!/usr/bin/perl -w
#
# This program searches for mp3's and in a draconing way, zaps them.
#
# William Julien
####
use strict;
use File::Find;
#
# declare our variables
#
my ($userid, # userid
$uid, # numerid uid
$username, # user name
$prompt, # prompt variable
$gecos, # contents of the gecos field
%userdata, # user information
@list, # list of all mp3 files
%filelist # list of mp3 files for a user
);
#
# get the directory by parameter or default to /home
#
@ARGV or push @ARGV, '/home';
#
# first get a list of userid's and user names on the system
#
open PASSWD, "/etc/passwd";
while (<PASSWD>) {
($userid, $uid, $gecos) = (split /:/)[0,2,4];
$username = (split /,/,$gecos)[0];
$userdata{$uid} = [ $userid, $username ];
}
close PASSWD;
#
# find all the mp3s
#
find (\&wanted, @ARGV);
#
# associate each filename with a user
#
foreach (@::list) {
$uid = (stat)[4];
if ( $uid > 0 ) {
push @{$filelist{$uid}}, $_;
}
}
#
# prompt for a uid
#
while(1) {
#
# display a list of userid's with mp3s
#
if ( scalar keys %filelist > 0 ) {
print "The following user(s) have mp3 files\n\n";
printf "%6s %8s %s\n", "uid","userid","username";
foreach $uid ( keys %filelist ) {
printf "%6d %8s %s\n", $uid,
${userdata{$uid}}[0],
${userdata{$uid}}[1];
}
} else {
print "No users have mp3 files\n";
exit;
}
print "\nEnter the uid to see the list of files (0 to exit)-> ";
$prompt = <STDIN>;
chomp($prompt);
$uid = $prompt;
if ( $prompt eq "0" ) {
print "Done\n";
exit;
}
if ( defined $filelist{$uid} ) {
foreach (@{$filelist{$uid}}) {
print "$_\n";
}
print "\nWould you like to zap them? (yes or no) -> ";
$prompt = <STDIN>;
chomp($prompt);
if ( $prompt eq "yes" ) {
foreach (@{$filelist{$uid}}) {
print "Zapping $_\n";
unlink $_;
}
delete $filelist{$uid};
} else {
print "Ok... The files are not zapped\n";
}
} else {
print "Sorry, not a valid uid\n";
}
}
# fin
###
#
# subroutine to find mp3 files
#
sub wanted {
my $uid;
if ( /mp3$/i ) {
push @::list, $File::Find::name;
}
}
Well, if you really want to search first, here is another version
of my program:
#!/usr/local/bin/perl
# Usage: listmp3 [dir...]
use warnings;
use File::Find ();
$searchdir = "/home";
push(@ARGV, $searchdir) unless (@ARGV);
$cols = 3;
$linewidth = 80; # Columns on terminal
$lines = 24; # Lines on terminal
$| = 1;
# Find all mp3 files and record under their owners:
%mp3 = ();
File::Find::find({wanted => \&wanted}, @ARGV);
for (;;) {
# Convert UIDs to usernames:
@users = ();
%uid = ();
foreach $uid (keys %mp3) {
push(@users, scalar(getpwuid($uid)));
$uid{getpwuid($uid)} = $uid;
}
if (!@users) {
print "No more mp3 files.\n";
last;
}
$width = int($linewidth/$cols) - 4;
$col = 0;
$line = 0;
foreach $i (1..@users) {
printf "%3i %-${width}s", $i, $users[$i-1];
$col = ($col + 1) % $cols;
if (!$col) {
print "\n";
$line = ($line + 1) % ($lines - 1);
if (!$line) {
print "Press RETURN to continue...";
$ans = <STDIN>;
}
}
}
print "\n" if ($col);
print "Enter user number or username (press RETURN to exit)? ";
chomp($user = <STDIN>);
last if ($user =~ /^\s*$/);
$user = $users[$user-1] if ($user =~ /^\d+$/);
$uid = $uid{$user};
next unless defined($uid);
$line = 0;
foreach $file (@{$mp3{$uid}}) {
print "$file\n";
$line = ($line + 1) % ($lines - 1);
if (!$line) {
print "Press RETURN to continue...";
$ans = <STDIN>;
}
}
$n = @{$mp3{$uid}};
if ($n == 1) {
print "Delete this file (y/n)? ";
} else {
print "Delete these $n files (y/n)? ";
}
chomp($ans = <STDIN>);
if ($ans =~ /^y/i) {
$done = unlink(@{$mp3{$uid}});
print "$done file(s) deleted\n";
delete $mp3{$uid};
}
}
sub wanted {
if (/^.*\.mp3\z/is) {
$owner = (stat($File::Find::name))[
push(@{$mp3{$owner}}, $File::Find::name);
}
}
Better. I have a few suggestions.
1. warnings.pm does not exist in perl prior to 5.6. It is better to just use the '-w' switch.
2. When you want to write production quality code, use should use the strict pragma. Particularly if this is to be run by root.
3. Now that you have a hash of the uid's after the find, why to you re-associate the uid->username map over, and over in your for loop. It would be better to do that once.
4. still doesn't work right. (I commented out the "use warnings, since this computer does not have 5.6, and added -w)
-->touch fred.mp3 tom.mp3 src/junk.mp3
-->listmp3.pl .
Use of uninitialized value at ./fred.pl line 87.
Use of uninitialized value at ./fred.pl line 87.
Argument "" isn't numeric in gpwuid at ./fred.pl line 26.
1 root 2 julien
Enter user number or username (press RETURN to exit)?
CyberMage: do you want my version
hacked to work with old versions
of perl, or do you have perl 5.6.0?
If you want "use strict" then change
the first few lines to:
use warnings;
use strict;
use File::Find ();
my ($searchdir, $cols, $linewidth, $lines, %mp3, @users, %uid,
$col, $line, $ans, $user, $uid, $n, $done, $owner,
$width, $i, $file);
$searchdir = "/home";
You shouldn't worry about reassociating
the uid->users map each time round
the main loop: this is very fast
compared with the time required
to print the list to the screen!
CyberMage,
I feel you should try both programs, or any others that come along and decide for yourself which approach better fits your needs. I normally only post comments. You can accept a comment as an answer.
Unless you require additonal features, I consider my code finished, tested, and bug free. I don't feel it is appropriate for me to tell you which one is better. That depends on whether it fits your requirements. You are a better judge of that than either of us.
You can tell what version of perl you have by typing 'perl -v'. It is unlikely that you have perl 5.6, unless you have a gung ho perl admin. It was just released.
You may want the code delivered via an email attachement. I have noticed that the proper indentation is bashed when cut-pasted from experts exchange. If so, just send me an email message to moonbeam@catmanor.com.
Martin,
On some systems, upgrading perl is not an option. When you work in a production environment you can't just simply willy-nilly replace a vendor supplied facility with a something you feel is better (even when you know you are right!). And in some companies, there are requirements for conformance to company standards. I like 5.6, and I run it at home. But I'd get my hand slapped if I installed it at work. Anyway, sysadmin utilities should be coded for portability.
I do agree that the warnings module in 5.6 is nice. It enables you to specify warning categories. However, since you are not specifing any categories (defaults to all), the "-w" option achieves the same purpose.
Finally, you still have a bug in there, somewhere. Try the following test (in an empty directory, just to be safe):
-->mkdir src
-->touch fred.mp3 tom.mp3 src/joe.mp3
-->listmp3 .
The strict pragma will also help you find some really elusive bugs. Unless you are dashing off some one-use trivial code, the use of the pragma is always a good idea. It helps those who have to maintain your code later on. Oh, and comments also help.
Change the wanted sub to:
sub wanted {
if (/^.*\.mp3\z/is) {
$owner = (stat($_))[4];
push(@{$mp3{$owner}}, $File::Find::name);
}
}
I agree with moonbeam: at this point, which program is "best" is up to you
to decide.
Re upgrading to perl 5.6.0, one large
university only recently installed
perl 5.6.0 as the default perl.
Before that it was perl4!
Even moonbeam's version won't work with perl4.
No comment has been added lately, so it's time to clean up this TA.
I will leave the following recommendation for this question in the Cleanup topic area:
Split: moonbeam {http:#4365294} & martin {http:#4369813}
Please leave any comments here within the next seven days.
PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!
jmcg
EE Cleanup Volunteer
Business Accounts
Answer for Membership
by: keirdPosted on 2000-09-16 at 18:47:32ID: 4329215
This sounds like a contract programming job, instead of a question.