views:

45

answers:

4

I am trying to create my first zsh completion script, in this case for the command netcfg.

Lame as it may sound I have stuck on the first hurdle, disclaimer, I know how to do this crudely, however I seek the "ZSH WAY" to do this.

I need to list the files in /etc/networking but only the files, not the directory component, so I do the following.

echo $(ls /etc/network.d/*(.))

/etc/network.d/ethernet-dhcp /etc/network.d/wireless-wpa-config

What I wanted was:

ethernet-dhcp wireless-wpa-config

So I try (excuse my naivity) :

echo ${(s/*\/)$(ls /etc/network.d/*(.))}

/etc/network.d/ethernet-dhcp /etc/network.d/wireless-wpa-config

It seems that this doesn't work, I'm sure there must be some clever way of doing this by splitting into an array and getting the last part but as I say, I'm complete noob at this.

Any advice gratefully received.

A: 

I'm used to seeing basename(1) stripping off directory components; also, you can use echo /etc/network/* to get the file listing without running the external ls program. (Running external programs can slow down completion more than you'd like; I didn't find a zsh-builtin for basename, but that doesn't mean that there isn't one.)

Here's something I hope will help:

haig% for f in $(echo /etc/network/*) ; do basename $f ; done
if-down.d
if-post-down.d
if-pre-up.d
if-up.d
interfaces
sarnold
The `${param#pattern}` parameter expansion is the way to go to remove prefixes. `${param%pattern}` to remove suffixes.
Chris Johnsen
+4  A: 

General note: There is no need to use ls to generate the filenames. You might as well use echo some*glob. But if you want to protect the possible embedded newline characters even that is a bad idea. The first example below globs directly into an array to protect embedded newlines. The second one uses printf to generate NUL terminated data to accomplish the same thing without using a variable.

It is easy to do if you are willing to use a variable:

typeset -a entries
entries=(/etc/network.d/*(.)) # generate the list
echo ${entries#/etc/network.d/}  # strip the prefix from each one

You can also do it without a variable, but the extra stuff to isolate individual entries is a bit ugly:

# From the inside, to the outside:
# * glob the entries
#   * NUL terminate them into a single string
# * split at NUL
#   * strip the prefix from each one
echo ${${(0)"$(printf '%s\0' /etc/network.d/*(.))"}#/etc/network.d/}

Or, if you are going to use a subshell anyway (i.e. the command substitution in the previous example), just cd to the directory so it is not part of the glob expansion (plus, you do not have to repeat the directory name):

echo ${(0)"$(cd /etc/network.d && printf '%s\0' *(.))"}
Chris Johnsen
Pity I've only got +1 to give. Three good options, all better than mine. :)
sarnold
A: 

Thanks for your suggestions guys, having done yet more reading of ZSH and coming back to the problem a couple of days later, I think I've got a very terse solution which I would like to share for your benefit.

echo ${$(print /etc/network.d/*(.)):t}

irishjava
+1  A: 

Chris Johnsen's answer is full of useful information about zsh, however it doesn't mention the much simpler solution that works in this particular case:

echo /etc/network.d/*(:t)

This is using the t history modifier as a glob qualifier.

Gilles