Simple (hopefully!!) shell program

15 views
Skip to first unread message

joe

unread,
Apr 17, 2002, 4:48:45 PM4/17/02
to
Hi,
I was wondering if I could get help with this problem. Basically I
need a list of all executable files in a directory. I'm totally lost
with it :( . Can any kind soul take pity on me and help?
Thanks


- List all executable files in all directories in the PATH environment
variable (i.e. list all programs that can be executed).
Usage: progs
or: progs (string)
Functionality:
If there are no arguments, list all programs that can be executed.
List them 1 per line, giving the full pathname of the file. Output
nothing else.
Sort the list.

If there is 1 argument, search for the argument as a string anywhere
in the list, ignoring case.
Output only the lines that match, nothing else.
If it does not match any lines, output nothing.
You can see that "progs (argument)" implements a more error-tolerant
"which" command. e.g. "progs edit" might yield something like:

/bin/edit
/bin/vedit
/usr/openwin/bin/editres
/usr/openwin/bin/iconedit
/usr/openwin/bin/textedit
/usr/openwin/bin/xedit

Doug Miller

unread,
Apr 17, 2002, 4:52:55 PM4/17/02
to
In article <300abea0.0204...@posting.google.com>, joe_o...@hotmail.com (joe) wrote:
>Hi,
> I was wondering if I could get help with this problem. Basically I
>need a list of all executable files in a directory. I'm totally lost
>with it :( . Can any kind soul take pity on me and help?
>Thanks
>
>
man ls
man grep

Sounds like homework. Come back when you've actually tried to solve the
problem. Show what you did, and what happened. Someone will help.

--
Real email address is alphageek /at/ milmac /dot/ com

.. Ted Kennedy's car has killed more people than my gun.

laura fairhead

unread,
Apr 17, 2002, 5:21:26 PM4/17/02
to

One way that you can split up the $PATH environment variable
is to use 'tr' to change the colons to SPACEs so that each
directory can be taken as a different argument to, say a 'for'
loop;

for dirname in `echo "$PATH" |tr ':' '\12'`
do
echo "$dirname"
done

Then you'll need to search each directory for executeable
files. You can loop throu` all the files using "$dirname"/*
(adding "$dirname"/.* for hidden files as well).

man sh [ most important you read this multiple times over ]
to see how you can glue it all together with shell
commands.

The test/[ command can test if a file is executeable or not
[ should be documented on the shell man page but also probably
under 'man test'.

One thing to note is that in a loop like;

for file in "$dirname"/* "$dirname"/.*
do :
done

If there are no non-hidden files in the directory "$dirname"/*
will be untouched by the shell so that you get one loop of
file=DIRNAME/*. This means you need to add an initial test to
ensure that $file is actually a file.


regards

--
laura fairhead # la...@madonnaweb.com http://lf.8k.com
# if you are bored crack my sig.
1F8B0808CABB793C0000666667002D8E410E83300C04EF91F2877D00CA138A7A
EAA98F30C494480157B623C4EF1B508FDED1CEFA9152A23DE35D661593C5318E
630C313CD701BE92E390563326EE17A3CA818F5266E4C2461547F1F5267659CA
8EE2092F76C329ED02CA430C5373CC62FF94BAC6210B36D9F9BC4AB53378D978
80F2978A1A6E5D6F5133B67B6113178DC1059526698AFE5C17A5187E7D930492

Andreas Kähäri

unread,
Apr 17, 2002, 5:32:29 PM4/17/02
to
Submitted by "Doug Miller" to comp.unix.shell:

> In article <300abea0.0204...@posting.google.com>, joe_o...@hotmail.com (joe) wrote:
>>Hi,
>> I was wondering if I could get help with this problem. Basically I
>>need a list of all executable files in a directory. I'm totally lost
>>with it :( . Can any kind soul take pity on me and help?
>>Thanks
>>
>>
> man ls
> man grep

man find

>
> Sounds like homework. Come back when you've actually tried to solve the
> problem. Show what you did, and what happened. Someone will help.


--
Andreas Kähäri
--------------------------------------------------------------
Feed your daemons. www.netbsd.org

Tony Lawrence

unread,
Apr 18, 2002, 2:27:56 PM4/18/02
to


Gawd I love it when they don't even bother to rewrite the assignment in
their own words..


--

Tony Lawrence
SCO/Linux Support Tips, How-To's, Tests and more: http://pcunix.com
Free Unix/Linux Consultants list: http://pcunix.com/consultants.html

Stephane CHAZELAS

unread,
Apr 18, 2002, 3:45:36 PM4/18/02
to
On 17 Apr 2002 13:48:45 -0700, joe <joe_o...@hotmail.com> wrote:
[...]

> - List all executable files in all directories in the PATH environment
> variable (i.e. list all programs that can be executed).
> Usage: progs
> or: progs (string)
> Functionality:
> If there are no arguments, list all programs that can be executed.
> List them 1 per line, giving the full pathname of the file. Output
> nothing else.
> Sort the list.
[...]

If your shell is zsh (anyway, there's no point using any other
one...), you don't need a script:

$ print -l ${^path}/*(-*N)
# lists every executable in PATH

$ print -l ${^path}/*edit*(-*N)
/usr/local/bin/bvedit
/usr/local/bin/gdb4editor
/usr/local/bin/mcedit
/usr/X11/bin/editres
/usr/X11/bin/xedit
/usr/bin/pstoedit

$ hash -m '*edit*'
gdb4editor=/usr/local/bin/gdb4editor
xedit=/usr/X11/bin/xedit
bvedit=/usr/local/bin/bvedit
editres=/usr/X11/bin/editres
mcedit=/usr/local/bin/mcedit
pstoedit=/usr/bin/pstoedit

--
Stéphane

Friedhelm Waitzmann

unread,
Apr 19, 2002, 10:06:00 PM4/19/02
to
In comp.unix.shell laura fairhead <la...@madonnaweb.com> writes:
>One way that you can split up the $PATH environment variable
>is to use 'tr' to change the colons to SPACEs so that each
>directory can be taken as a different argument to, say a 'for'
>loop;

>for dirname in `echo "$PATH" |tr ':' '\12'`
>do
> echo "$dirname"
>done

No, you can't. Imagine PATH containing the string
/bin:/my directory:/usr/bin:/usr/bin/fac*
^ ^
yes, a space character! yes, an asterisk!

then your 'for' loop yields

bin
/my <--- output from tr broken at white space
directory
/usr/bin
/usr/bin/factor
^^^ this is generated by the asterisk

Try

IFS=: # break strings at colons
set -f # disable filename generation -- leave asterisk untouched
set x; shift # empty positional parameters
set -- $PATH
set +f; set -"$flags"
for dirname
do
echo "$dirname"
done

result ok:

bin
/my directory
/usr/bin
/usr/bin/fac*

Friedhelm Waitzmann

unread,
Apr 19, 2002, 10:19:57 PM4/19/02
to
In comp.unix.shell laura fairhead <la...@madonnaweb.com> writes:
>One way that you can split up the $PATH environment variable
>is to use 'tr' to change the colons to SPACEs so that each
>directory can be taken as a different argument to, say a 'for'
>loop;

>for dirname in `echo "$PATH" |tr ':' '\12'`
>do
> echo "$dirname"
>done

No, you can't. Imagine PATH containing the string


/bin:/my directory:/usr/bin:/usr/bin/fac*
^ ^
yes, a space character! yes, an asterisk!

then your 'for' loop yields

bin
/my <--- output from tr broken at white space
directory
/usr/bin
/usr/bin/factor
^^^ this is generated by the asterisk

Try

IFS=: # break strings at colons
set -f # disable filename generation -- leave asterisk untouched
set x; shift # empty positional parameters
set -- $PATH
set +f

laura fairhead

unread,
Apr 20, 2002, 9:41:27 AM4/20/02
to
On 20 Apr 2002 02:19:57 GMT, Friedhelm Waitzmann <Friedhelm...@web.de> wrote:

>In comp.unix.shell laura fairhead <la...@madonnaweb.com> writes:
>>One way that you can split up the $PATH environment variable
>>is to use 'tr' to change the colons to SPACEs so that each
>>directory can be taken as a different argument to, say a 'for'
>>loop;
>
>>for dirname in `echo "$PATH" |tr ':' '\12'`
>>do
>> echo "$dirname"
>>done
>
>No, you can't. Imagine PATH containing the string
>/bin:/my directory:/usr/bin:/usr/bin/fac*
> ^ ^
> yes, a space character! yes, an asterisk!

Sure, it can also breaks where 'echo' interprets backslashes and where
'echo' interprets '-n' and '-e' options.

>
>then your 'for' loop yields
>
>bin
>/my <--- output from tr broken at white space
>directory
>/usr/bin
>/usr/bin/factor
> ^^^ this is generated by the asterisk
>
>Try
>
>IFS=: # break strings at colons
>set -f # disable filename generation -- leave asterisk untouched
>set x; shift # empty positional parameters
>set -- $PATH
>set +f
>for dirname
>do
> echo "$dirname"
>done
>
>result ok:
>
>bin
>/my directory
>/usr/bin
>/usr/bin/fac*

Okay, this should work with everything as far as I can see...

I would save/restore IFS and avoid 'set --' (some older systems
don't support it and you can rewrite it to not break backwards
compatibility), and then I'd get the following code;

OIFS=$IFS # save IFS
: ${IFS-`unset OIFS`}


IFS=: # break strings at colons
set -f # disable filename generation

set x; shift # empty positional parameters

set x $PATH; shift # set to PATH elements
set +f
IFS=$OIFS # restore IFS
: ${OIFS-`unset IFS`}

for dirname # loop for each PATH element
do
echo "$dirname"
done

Now I'd wonder how to do it in a way where you weren't using
$@ for the PATH elements (although most of the time it will be alright
to use $@ I'm sure...)

wishes

Stephane CHAZELAS

unread,
Apr 20, 2002, 1:16:20 PM4/20/02
to
On Sat, 20 Apr 2002 13:41:27 GMT, laura fairhead <la...@madonnaweb.com> wrote:
[...]

> OIFS=$IFS # save IFS
>: ${IFS-`unset OIFS`}

Probably useless: command substitution is run in a subshell

A=1
unset B
: ${B-`unset A`}
echo $A
--> 1

Rather
case ${IFS+1} in
"") unset OIFS;;
esac

> IFS=: # break strings at colons
> set -f # disable filename generation
> set x; shift # empty positional parameters

What's the use of it?

> set x $PATH; shift # set to PATH elements
> set +f
> IFS=$OIFS # restore IFS
>: ${OIFS-`unset IFS`}

Same thing as above.

> for dirname # loop for each PATH element
> do
> echo "$dirname"
> done
>
> Now I'd wonder how to do it in a way where you weren't using
> $@ for the PATH elements (although most of the time it will be alright
> to use $@ I'm sure...)

Note that after command substitution, there's no filename
generation:

IFS=:
for dirname in `cat <<EOF
$PATH
EOF`; do...

But there's the problem of trailing LF removal.

Anyway, if you have spaces, stars or LF in your PATH, you're
probably already in big trouble.

--
Stéphane

laura fairhead

unread,
Apr 21, 2002, 4:04:10 PM4/21/02
to
On 20 Apr 2002 17:16:20 GMT, Stephane CHAZELAS <stephane...@yahoo.fr> wrote:

>On Sat, 20 Apr 2002 13:41:27 GMT, laura fairhead <la...@madonnaweb.com> wrote:
>[...]
>> OIFS=$IFS # save IFS
>>: ${IFS-`unset OIFS`}
>
>Probably useless: command substitution is run in a subshell
>
>A=1
>unset B
>: ${B-`unset A`}
>echo $A
>--> 1
>

I had tested one version, then made a nicer version and posted that
instead :)
You're right of course, the 'unset' will be ineffective in there :{

>Rather
>case ${IFS+1} in
> "") unset OIFS;;
>esac

Neat. For a one-liner you could use 'test' ;

[ -n "${IFS+1}" ] || unset OIFS

>
>> IFS=: # break strings at colons
>> set -f # disable filename generation
>> set x; shift # empty positional parameters
>
>What's the use of it?

That line was left from Friedhelm Waitzmann's original code, I
left it in for safety because I hadn't yet confirmed the reason
why it was there. In the previous code 'set -- $PATH' was used
and if PATH is a nul string this may not leave the positional
parameters cleared, so it's clearing them explicity first. In
my code here it isn't necessary thou`...

>
>> set x $PATH; shift # set to PATH elements
>> set +f
>> IFS=$OIFS # restore IFS
>>: ${OIFS-`unset IFS`}
>
>Same thing as above.
>

>> for dirname # loop for each PATH element
>> do
>> echo "$dirname"
>> done
>>
>> Now I'd wonder how to do it in a way where you weren't using
>> $@ for the PATH elements (although most of the time it will be alright
>> to use $@ I'm sure...)
>
>Note that after command substitution, there's no filename
>generation:
>

You're mistaken here, there is filename expansion as long as
it's not part of an assignement word.

>IFS=:
>for dirname in `cat <<EOF
>$PATH
>EOF`; do...
>
>But there's the problem of trailing LF removal.

Quite an irritating problem at times...

>
>Anyway, if you have spaces, stars or LF in your PATH, you're
>probably already in big trouble.

Sure but it may have other applications.

The most elegant way I could find to do it was with the POSIX
string splicing parameter expansions;

s=$PATH:
while
f=${s%%:*}
s=${s#*:}
[ -n "$f" ]
do
echo "directory=[$f]"
done

It gets a bit awful doing it without those but it could be
done with 'sed' (and a few eval's )

The other way is the obvious but messy;

set -f
OIFS=$IFS
[ -n "${IFS+1}" ] || unset OIFS
IFS=:
for f in /../ $PATH
do
if [ "x$f" = "x/../" ]
then
set +f
IFS=$OIFS
[ -n "${OIFS+1}" ] || unset IFS
continue
fi
echo "directory=[$f]"
done

>
>--
>Stéphane

byefrom

Stephane CHAZELAS

unread,
Apr 21, 2002, 6:59:27 PM4/21/02
to
On Sun, 21 Apr 2002 20:04:10 GMT, Laura wrote:
[...]

>>Rather
>>case ${IFS+1} in
>> "") unset OIFS;;
>>esac
>
> Neat. For a one-liner you could use 'test' ;
>
> [ -n "${IFS+1}" ] || unset OIFS

Or
${IFS+false} : && unset OIFS

I like "case", because we are sure that it doesn't spawn a new
process even with early Bourne shells, and there's no problem
if variables are left unquoted.

[...]


>>Note that after command substitution, there's no filename
>>generation:
>>
>
> You're mistaken here, there is filename expansion as long as
> it's not part of an assignement word.

[...]

Right. I was probably mistaken because I tried it with zsh.

Anyway. I really find this "feature" ridiculous, and I totally
agree with zsh choices on this (no filename generation nor word
splitting after variable expansion, and no filename generation
after command substitution).

--
Stéphane

Friedhelm Waitzmann

unread,
Apr 22, 2002, 10:57:36 AM4/22/02
to
In comp.unix.shell laura fairhead <la...@madonnaweb.com> writes:

[echo "$PATH"]

>Sure, it can also breaks where 'echo' interprets backslashes and where
>'echo' interprets '-n' and '-e' options.

Then use `printenv PATH' rather than `echo "$PATH"'.

[using $@ to store the elements of PATH]

>Now I'd wonder how to do it in a way where you weren't using
>$@ for the PATH elements (although most of the time it will be alright
>to use $@ I'm sure...)

Two possibilities:

(1) Do it in a sub shell.

If not possible,

(2) use the sed call described in

Newsgroups: comp.unix.shell
From: hei...@DrB.Insel.DE (Heiner Marxen)
Subject: Re: Quoting problems in script parameter
Message-ID: <Gutvr...@DrB.Insel.DE>
Date: Fri, 19 Apr 2002 18:37:57 GMT

to save $@ before and restore it afterwards:

# save $@
quoted_argv=x
for word
do
# convert each word in a shell token and append it to
# $quoted_argv
quoted_argv="$quoted_argv `WORD="$word" printenv WORD \
| sed -e "s/'/&\\\\&&/g" -e "1s/^/'/" -e "\$s/\$/'/"`"
done
# Now $quoted_argv contains a list x token1 token2 ... of
# shell tokens separated by white space.

# restore $@
eval 'set '"$quoted_argv"
shift # cut off the leading x

Regards,
Friedhelm.

laura fairhead

unread,
Apr 22, 2002, 2:10:01 PM4/22/02
to
On 22 Apr 2002 14:57:36 GMT, Friedhelm Waitzmann <Friedhelm...@web.de> wrote:

>In comp.unix.shell laura fairhead <la...@madonnaweb.com> writes:
>
>[echo "$PATH"]
>
>>Sure, it can also breaks where 'echo' interprets backslashes and where
>>'echo' interprets '-n' and '-e' options.
>
>Then use `printenv PATH' rather than `echo "$PATH"'.

Or you can use a here-document.

>
>[using $@ to store the elements of PATH]
>
>>Now I'd wonder how to do it in a way where you weren't using
>>$@ for the PATH elements (although most of the time it will be alright
>>to use $@ I'm sure...)
>
>Two possibilities:
>
>(1) Do it in a sub shell.
>
>If not possible,
>
>(2) use the sed call described in
>
> Newsgroups: comp.unix.shell
> From: hei...@DrB.Insel.DE (Heiner Marxen)
> Subject: Re: Quoting problems in script parameter
> Message-ID: <Gutvr...@DrB.Insel.DE>
> Date: Fri, 19 Apr 2002 18:37:57 GMT
>

Also described here BTW;

From: la...@madonnaweb.com (laura fairhead)
Newsgroups: comp.unix.shell
Subject: Re: No hyphens in bash variables
Date: Mon, 08 Apr 2002 20:46:58 GMT
Message-ID: <3cb1fef4...@NEWS.CIS.DFN.DE>

> to save $@ before and restore it afterwards:
>
> # save $@
> quoted_argv=x
> for word
> do
> # convert each word in a shell token and append it to
> # $quoted_argv
> quoted_argv="$quoted_argv `WORD="$word" printenv WORD \
> | sed -e "s/'/&\\\\&&/g" -e "1s/^/'/" -e "\$s/\$/'/"`"
> done
> # Now $quoted_argv contains a list x token1 token2 ... of
> # shell tokens separated by white space.
>
> # restore $@
> eval 'set '"$quoted_argv"
> shift # cut off the leading x
>

Sure beats the way described in the FAQ to do it :)

All the backslashes in your 'sed' commands need to be doubled thou`
because of the grave quote substitution undoubling them once
before the shell does it again.

I'd use a here-document and get rid of some of the backslashes
via single quoting;

word=`sed "s/'/&"'\\\\&&/g;\$s/\$/'"'/" <<:
$word
:
`
quoted_argv="$quoted_argv '$word"

This would definently make a good pair of functions :-)

>Regards,
>Friedhelm.

laura fairhead

unread,
Apr 22, 2002, 2:13:09 PM4/22/02
to
On 21 Apr 2002 22:59:27 GMT, Stephane CHAZELAS <stephane...@yahoo.fr> wrote:

>On Sun, 21 Apr 2002 20:04:10 GMT, Laura wrote:
>[...]
>>>Rather
>>>case ${IFS+1} in
>>> "") unset OIFS;;
>>>esac
>>
>> Neat. For a one-liner you could use 'test' ;
>>
>> [ -n "${IFS+1}" ] || unset OIFS
>
>Or
>${IFS+false} : && unset OIFS
>
>I like "case", because we are sure that it doesn't spawn a new
>process even with early Bourne shells, and there's no problem
>if variables are left unquoted.

How about using 'eval' then ?

eval "${IFS+:} unset OIFS"


seeyafrom

Stephane CHAZELAS

unread,
Apr 22, 2002, 4:18:27 PM4/22/02
to
On Mon, 22 Apr 2002 18:13:09 GMT, laura fairhead wrote:
[...]

>>> [ -n "${IFS+1}" ] || unset OIFS
>>
>>Or
>>${IFS+false} : && unset OIFS
[...]

> How about using 'eval' then ?
>
> eval "${IFS+:} unset OIFS"

You don't need eval:

${IFS+:} unset OIFS

It will now be difficult to find shorter :)

--
Stéphane

Reply all
Reply to author
Forward
0 new messages