Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight

very new to perl; suggestions for porting this shell script to perl?

by sinusoid (Initiate)
on May 27, 2011 at 19:39 UTC ( #907057=perlquestion: print w/replies, xml ) Need Help??
sinusoid has asked for the wisdom of the Perl Monks concerning the following question:

I have very limited experience in any sort of scripting other than linux shell scripts. I wrote a shell script to generate a menu for a linux window manager called pekwm, but it wasn't quite fast enough to be used as a "dynamic menu entry" so i've been using it to generate a text file read by the menu on a per-need basis. Then i came across perl and noticed how much more speed the scripts seem to be capable of achieving and decided to try to port the shell script to perl. I do not want to add any functionality, i just have a feeling it could be much more efficient, as i wrote based on nothing but a few relevant examples I came across. For a more detailed description on how this script is supposed to work see the very bottom. First i'll post the shell script (which i don't believe needs any refinement, followed by what i've managed in perl..

Shell Script:
CATS="Audio Graphics Network Settings System Utility" for CAT in $CATS; do echo " Submenu = \"$CAT\" {" for CATMATCH in `grep -l "^Categories=.*$CAT.*" /usr/share/applica +tions/*.desktop`; do name=`sed -n '1,/^Name=/ s/^Name=//p' <$CATMATCH` exec=`sed -e 's/ %[UuFf]//' -ne '1,/^Exec=/ s/^Exec=//p' < +$CATMATCH` echo " Entry = \"$name\" { Actions = \"Exec $exec\" + }" done echo " }" done ##SEDLESS EQUIVALENT## #CATS="Audio Graphics Network Settings System Utility" #for CAT in $CATS; do # echo " Submenu = \"$CAT\" {" # for CATMATCH in `grep -l "^Categories=.*$CAT.*" /usr/share/applic +ations/*.desktop`; do # name=`grep "^Name=" $CATMATCH` # exec=`grep "^Exec=" $CATMATCH`; exec=${exec% %[FfUu]} # echo " Entry = \"${name#Name=}\" { Actions = \"Exe +c ${exec#Exec=}\" }" # done # echo " }" #done

One thing I was unable to figure out how to emulate in perl was the option to specify a set of expressions or line numbers fuctioning as the start/stop boundaries for where the string search/replace should happen. this was achieved in the shell script above using "name=`sed -n '1,/^Name=/ s/^Name=//p' <$CATMATCH`", where "1,/^Name=/" specifies the boundaries.

Perl Script:
@Cats = ("Audio","Graphics","Network","Settings","System","Utility"); $Dir = "/usr/share/applications"; opendir(DIR,"$Dir"); @Files=readdir(DIR); foreach $Cat (@Cats) { print "Submenu = \"$Cat\" { \n"; foreach $File (@Files) { open FILE, "$Dir/$File"; while (<FILE>) { if(/Categories=.*$Cat.*/) { @Matches = $File; foreach $Match (@Matches) { open MATCH, "$Dir/$Match"; while (<MATCH>) { #$string = $_; @Names = $_=~ (/^Name=(.*)/o); @Execs = $_=~ (/^Exec=(.*)/o); foreach $Name (@Names) { print " Entry = \"$Name\" "; }; foreach $Exec (@Execs) { $Exec =~ s/(.*) %[uUfF]/$1/; print "{ Actions = \"Exec $Exec\" }\n"; }; }; }; }; }; }; print "}\n"; };

The script generates a menu (actually, a part of a menu) by matching the desired list of categories (which are also used as names for each sub-menu entry during generation) against those specified in the /usr/share/applications/*.desktop files. When a match is found, it reports the name of which xxx.desktop file the match occured and extracts from that file the name of the application and the command used to run it. It then prints the necessary menu syntax stuff, adding from variables the name of the sub-menu (taken from the matched category), the name of the application (taken from the name extracted from the xxx.desktop file), and the command used to execute that application (also extracted from the xxx.desktop file) for each particular file of each particular submenu. Thanks in advance for any sort of help you can give!

Replies are listed 'Best First'.
Re: very new to perl; suggestions for porting this shell script to perl?
by Cristoforo (Curate) on May 28, 2011 at 16:25 UTC
    Not knowing Unix systems, I can't offer too much help. It would be very helpful to provide about 50+ lines of your output to show how you want the results of the program. I'm sure there are many improvements that could be made. For one, the line:


    It could be better stated:

    my @Files = grep /\.desktop$/, readdir DIR;

    grep will only pass *.desktop files to @Files

    If you plan on writing Perl scripts, it would be advisable to declare your variables, (usually with 'my'), and to include the lines (at the beginning of your script):

    #!/usr/bin/perl use strict; use warnings;

    These will tell you of errors in your code syntax, spelling mistakes, etc. You will find nearly all the code on this site follow that method.

    Update: It would also be helpful to see some of the input files, in paticular, lines that match Categories and Name and Exec.

    One last thing - it would be nice to enclose these sample inputs and outputs in <readmore>. . .</readmore> tags so your post doesn't take so much space. :-)

      That first suggestion is very helpful. 2 completely irrelevant files were being searched.

      If you're more experienced with windows, those desktop files are basically like a more detailed version of a windows shortcut file. They're installed to /usr/share/applications upon installing most linux gui apps.

      Here are the contents of gimp.desktop.


      Here is what's generated by the script from my .desktop files:

      With certain applications I had previously installed, the desktop files would occasionally contain one or more sections in addition to the [Desktop Entry] section , which would often contain another Name=xxxx and Exec=xxxx line. This is why I would like to find a way to either limit the regex match to only the first instance or else be able to specify a start/stop range for the matching.

      I appreciate your willingness to help despite not having much to go by. Thanks alot.

        Yes, the sample files and output clarified the problem.

        Here is a solution, but you should probably check it to see if it meets your specifications.

        #!/usr/bin/perl use strict; use warnings; my @files = glob "/usr/share/applications/*.desktop"; my $regex = join "|", qw/ Audio Graphics Network Settings System Utili +ty /; my %data; FILE: for my $file (@files) { open my $fh, "<", $file or die "Unable to open $file (for reading) +. $!"; my ($category, $name, $exec); while (<$fh>) { if(/^Categories=.*?\b($regex)\b/) { $category ||= $1; # sets cat for first time only (each fil +e) } elsif (/^Name=(.+)$/) { $name ||= $1; # sets name first time only (each file) } elsif (/^Exec=(\S+)/) { $exec ||= $1; # sets exec first time only (each file) } } close $fh or die "Unable to close $file. $!"; for ($category, $name, $exec) { if (! defined) { warn "$file missing category or name or exec.\n"; # skip the 'push' below and go to the label 'FILE:' next FILE; } } push @{ $data{ $category } }, {name => $name, exec => $exec}; } for my $category (keys %data) { print "Submenu = \"$category\" {\n"; my @recs = @{ $data{ $category } }; for my $href ( @recs ) { # hash reference print " Entry = \"$href->{name}\" { Actions = \"$href->{ex +ec}\" }\n"; } print "}\n"; }

        Notice how all the variables are declared with my. Declaring variables this way helps catch spelling errors and also limits the scope to the innermost containing block. This property is handy and can be seen in the declaration of my ($category, $name, $exec)

        Every time the for loop starts a new iteration, (with a new file), it gets a fresh copy of 'undefined' variables. Then, later on they are assigned to, ($category ||= $1;). The first time a valid match is made, an assignment will happen. If there are more than 1 instance of name or exec (as you noted in your post), they will be ignored. This is because any of the three already has the first match assigned to it.

        The data is stored in the %data hash (which is then printed out when the loop terminates).

        The glob function does wildcard expansion and returns all the matching files. (gimp.desktop and all other files with the .desktop extension).

        There are many other Perl idioms here and if you have questions about just why a certain piece is written the way it is, just ask.

        If you plan on using Perl now on, it would be probably helpful to read a book such as Learning Perl witch is a good intro to Perl or Programming Perl which is larger but a good reference book.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://907057]
Approved by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (7)
As of 2018-06-21 08:51 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (117 votes). Check out past polls.