pipe to ls with filenames with spaces breaks!

I wanted an alias or program, lsd, that would show just the directories in a directory. My first take was this alias.

alias lsd='ls -d `find . -maxdepth 1 -type d -print | grep -v "^.$" | cut -c 3- `'

It worked fine until I got to directory names with spaces, so I moved it into a script so I could look at it, and as a first take, thought I'd use sed to replace any spaces with "\ " like you would from a command line and that was a disaster. I finally got it to work, and I'll include the short script below, but does someone know of a better, shorter way of doing it? (The
section on args is there so later I can override the depth if I want to.

One strange thing is that the grep is to get rid of the ./ dir which is always there, and I don't really need to see it. Great! But if there's no directories in a directory, it lists the ./ anyway! I kind of like it, but I'd like to understand how or why!

#! /bin/bash
export IFS="
"
mygetdir='.'
for arg in $* ; do
case $arg in
*) mygetdir=$arg ;;
esac
done
cd >& /dev/null $mygetdir
echo ~~~ $mygetdir ~~~
ls -F -d `find -L . -maxdepth 1 -type d -print | sed 's/.\///' | grep -v "^.$"`

If the only directory is ./ then the pipeline has no output, and the default directory for ls with no arguments is ./ -- so -- accidentally I got behavior I want. I'd still like to have a shorter more elegant version though.

Thanks,

Patrick

Why not use the fact that ls -F suffixes directories with a "/" to filter them out, something like this perhaps:

ls -F "$@" | sed -n 's/\/$//p'

You can explicitly exclude the current (dot) directory with something like

find . -maxdepth 1 -name . -o -type d -print | sed -e 's%^./%%' | tr '\012' '\000' | xargs -r0 ls -d

(Alternate sed invocation just to show There's More Than One Way To Do It.)

The use of tr to replace newlines with ASCII NUL is so I can use xargs -0 in order to cope with spaces in file names. It still won't work right if you have directory names with newlines in them, but I guess that's an acceptable restriction in practice.

Why do you want to pass this to ls -d, though? It will wrap the output into columns which is kind of neat, but using ls just for this side effect is kind of obscure. (Maybe something like pr -bt4 would be more transparent, although ls is more flexible, as it does some dynamic formatting of the column widths depending on the lengths of the entries.)

That's it exactly. ls does more flexible formatting, giving more columns with shorter filenames. If there was a pr -auto that worked liked that it would be cool:)

Patrick

Just the thing! Thank you! This lets me do it more simply, and lets me collect args for the ls so that for example, sometimes I could do lsd, sometimes lsd -a. I've decided to build an arg string bit by bit so that later if some args will be better suited to the script, and some to the ls, then I can dispatch appropriate args appropriatelly;) Here's the most recent incarnation of lsd:

#! /bin/bash
export IFS="
"
mylsargs="-F"
mygetdir='.'
for arg in $* ; do
case $arg in
-a) mylsargs="$mylsargs -a" ;;
*) mygetdir=$arg ;;
esac
done
cd $mygetdir
if [ "$mygetdir" != "." ]; then
echo ~~~ $mygetdir ~~~
fi
ls -F -d $(eval ls $mylsargs . | sed -n 's/\/$//p')

As a minor quib, you should probably prefer "$@" over $*

My version of find permits formating output like in:

find . -maxdepth 1 ! -name . -and -type d -printf "%f\n"

Much more format directives available in man find.

@era: to explicitly exclude the dot from the list, shoudn't you use the condition
! -name . -a -type d instead of -name . -or -type d ?

Works for me. It's common that "this or that" is used as a shortcut for "if not this then that".

This is cool! I'm sure I have something to learn here, because I had thought that outside of double quotes, $@ and $* were identical. Quoting from the Bash Reference Manual:

So, please, I've always wondered about this--why should I prefer to use one over the other, and in what circumstances--Does it matter outside of double quotes, and what the heck do the descriptions of the behavior within double quotes mean?

Patrick

set -- ${*:-.}
for x
do
   [ -d "$x" ] && ls -ld ${x:+$x/}*/ 2>/dev/null
done

Or:

[ -n "$1" ] && cd "$1" || return 1
printf "%s\n" "$PWD"/*/

Or:

[ -n "$1" ] && cd "$1" || return 1
ls -ld "$PWD"/*/

Etc....

So following up, this script:

#!/bin/bash
export IFS="
"
echo "$@"
echo "$*"
for arg in $@ ; do
    echo $arg
done
for arg in $* ; do
    echo $arg
done
for arg in "$@" ; do
    echo $arg
done
for arg in "$*" ; do
    echo $arg
done

has this output:

./shtest a b c
a b c
a
b
c
a
b
c
a
b
c
a
b
c
a b c

So, seems the same outside quotes, but what the heck is going on inside the quotes? When would I want the join them all together behavior and when would I want the individual words behavior?

Patrick

Try it with:

./shtest a "b c" d

Use $* or $@ when you want the individual words as separate parameters.

Use "$*" when you want all the words as a single argument.

Use "$@" when you want each argument (which may contain spaces) as a separate parameter.

Or in simpler terms, historically, $* was what they had originally, but it didn't work right for arguments containing embedded whitespace, and so they had to invent "$@" for passing through quoted arguments correctly.