Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

List all directories without sub-directories

203 views
Skip to first unread message

James

unread,
Aug 25, 2016, 7:07:30 PM8/25/16
to
How to list directories (in a tree structure) which have no sub-directories?

TIA
James

Kaz Kylheku

unread,
Aug 25, 2016, 8:20:24 PM8/25/16
to
$ ./txr
This is the TXR Lisp interactive listener of TXR 147.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (ftw "/etc/network"
(lambda (path type stat . other-args)
(when (and (eql type ftw-d) (eql stat.nlink 2))
(put-line path))))
/etc/network/if-down.d
/etc/network/if-up.d
/etc/network/if-pre-up.d
/etc/network/if-post-down.d
/etc/network/run/static-network-up-emitted
t

Ed Morton

unread,
Aug 25, 2016, 11:37:19 PM8/25/16
to
This should do it assuming all directories are readable and otherwise "normal":

find . -type d -print0 |
awk -v RS='\0' '
{ delete empty[gensub("/[^/]+$","",1)]; empty[$0] }
END { for (dir in empty) print dir }
'

It finds all directories, saves them all in an array "empty" and deletes the
parent of the current directory if it existed in "empty". Then in the END all
you have left to print are the directories that had no children as every child
deleted it's parent from the array when it was read.

Check the order that find prints the directories in on your system. I THINK with
no options it'll always print the parent then visit the children but check it
and throw in whatever option or sort command you need to make that happen.

Ed.

Ed Morton

unread,
Aug 25, 2016, 11:39:04 PM8/25/16
to
On 8/25/2016 10:37 PM, Ed Morton wrote:
> On 8/25/2016 6:07 PM, James wrote:
>> How to list directories (in a tree structure) which have no sub-directories?
>>
>> TIA
>> James
>>
>
> This should do it assuming all directories are readable and otherwise "normal":
>
> find . -type d -print0 |
> awk -v RS='\0' '
> { delete empty[gensub("/[^/]+$","",1)]; empty[$0] }
> END { for (dir in empty) print dir }
> '

Oh, and the above uses GNU awk for gensub(), with other awks you'd use sub() and
a variable.

Rakesh Sharma

unread,
Aug 26, 2016, 1:07:53 AM8/26/16
to
On Friday, 26 August 2016 04:37:30 UTC+5:30, James wrote:

>
> How to list directories (in a tree structure) which have no sub-directories?
>

find . -type d -empty

Rakesh Sharma

unread,
Aug 26, 2016, 1:17:34 AM8/26/16
to
On Friday, 26 August 2016 04:37:30 UTC+5:30, James wrote:

>
> How to list directories (in a tree structure) which have no sub-directories?
>


find . -type d -exec sh -c '
case $(find "$1" -maxdepth 1 -type d -print -quit) in "") false;; esac
' {} {} \; -print

Casper H.S. Dik

unread,
Aug 26, 2016, 2:17:21 AM8/26/16
to
James <hsle...@yahoo.com> writes:

>How to list directories (in a tree structure) which have no sub-directories?


find . -type d -links 2


(A directory has at least two links; and one additional for each sub
directory)

Casper

Rakesh Sharma

unread,
Aug 26, 2016, 4:06:30 AM8/26/16
to
find . -type d \( -empty -o -exec sh -c '
cd "$1" && \
case $(find . -maxdepth 1 ! -name . -type d -print -quit) in
"") :;;
* ) false;;
esac
' {} {} \; \) -print

Kenny McCormack

unread,
Aug 26, 2016, 8:35:42 AM8/26/16
to
In article <60529f58-3fce-4d68...@googlegroups.com>,
Here's my general approach. I think (but am not certain) that it is the
same general approach as Ed's method.

% find -type d | gawk '{x[$0]} END { for (i in x) for (j in x) if (i \!= j && j ~ i) { delete x[i];break } for (i in x) print i}'

Developed using GNU/Linux tools, but should be pretty much generic (modulo
the need to use "find ." instead of just "find" if using non-GNU find).

The backslash is needed if using csh; may or may not be needed if using bash.

P.S. Caspar's method, using -links, is really quite natty - I'd vote it
the best solution so far - but note that it probably won't work on
non-Unix-ish filesystems. I just did some testing on a Samba-mounted
Windows (NTFS) filesystem, and the various tools always report the link
count for a directory as "1" (regardless of the actual state of the
directory).

P.P.S. It'd be nice to hear back from OP - among other things to learn
what the actual point of this thread was.

--
The scent of awk programmers is a lot more attractive to women than
the scent of perl programmers.

(Mike Brennan, quoted in the "GAWK" manual)

Ed Morton

unread,
Aug 26, 2016, 9:55:33 AM8/26/16
to
On 8/26/2016 7:35 AM, Kenny McCormack wrote:
> In article <60529f58-3fce-4d68...@googlegroups.com>,
> James <hsle...@yahoo.com> wrote:
>> How to list directories (in a tree structure) which have no sub-directories?
>>
>> TIA
>> James
>
> Here's my general approach. I think (but am not certain) that it is the
> same general approach as Ed's method.
>
> % find -type d | gawk '{x[$0]} END { for (i in x) for (j in x) if (i \!= j && j ~ i) { delete x[i];break } for (i in x) print i}'

That will be significantly slower than my approach since it has additional
nested loops through all directories and will fail when the directory names
contain RE metacharacters due to the RE comparison of "j ~ i", e.g. ("abc" !=
"a.c" && "abc" ~ "a.c") is true for the 2 separate directories "abc" and "a.c".
and so would result in "abc" being deleted from the array even if it were empty.

Ed.

Ed Morton

unread,
Aug 26, 2016, 9:57:57 AM8/26/16
to
On 8/26/2016 1:17 AM, Casper H.S. Dik wrote:
> James <hsle...@yahoo.com> writes:
>
>> How to list directories (in a tree structure) which have no sub-directories?
>
>
> find . -type d -links 2

When I run that on cygwin it produces no output:

$ find . -type d -links 2
$

Regards,

Ed.

Ed Morton

unread,
Aug 26, 2016, 9:59:46 AM8/26/16
to
That lists the directories that contain neither files nor directories, not just
those that have no sub-directories.

Ed.

Janis Papanagnou

unread,
Aug 26, 2016, 10:23:29 AM8/26/16
to
On 26.08.2016 15:57, Ed Morton wrote:
> On 8/26/2016 1:17 AM, Casper H.S. Dik wrote:
>> James <hsle...@yahoo.com> writes:
>>
>>> How to list directories (in a tree structure) which have no sub-directories?
>>
>> find . -type d -links 2

This is indeed a very clever way to solve the issue. Kudos!

>
> When I run that on cygwin it produces no output:

It works for me also on Cygwin.

Janis

Ed Morton

unread,
Aug 26, 2016, 10:55:50 AM8/26/16
to
On 8/26/2016 9:23 AM, Janis Papanagnou wrote:
> On 26.08.2016 15:57, Ed Morton wrote:
>> On 8/26/2016 1:17 AM, Casper H.S. Dik wrote:
>>> James <hsle...@yahoo.com> writes:
>>>
>>>> How to list directories (in a tree structure) which have no sub-directories?
>>>
>>> find . -type d -links 2
>
> This is indeed a very clever way to solve the issue. Kudos!
>
>>
>> When I run that on cygwin it produces no output:
>
> It works for me also on Cygwin.

$ mkdir tmp
$ mkdir tmp/foo

$ find tmp -type d -print0 | awk -v RS='\0' '{delete
empty[gensub("/[^/]+$","",1)]; empty[$0]} END{for (dir in empty) print dir}'
tmp/foo

$ find tmp -type d -links 2
$

Maybe different versions of something? I'm running

$ bash --version
GNU bash, version 4.3.42(3)-release (x86_64-unknown-cygwin)

$ find --version
find (GNU findutils) 4.5.12

on Windows 7.

Ed.

Janis Papanagnou

unread,
Aug 26, 2016, 11:07:33 AM8/26/16
to
On 26.08.2016 16:55, Ed Morton wrote:
> On 8/26/2016 9:23 AM, Janis Papanagnou wrote:
>> On 26.08.2016 15:57, Ed Morton wrote:
>>> On 8/26/2016 1:17 AM, Casper H.S. Dik wrote:
>>>> James <hsle...@yahoo.com> writes:
>>>>
>>>>> How to list directories (in a tree structure) which have no sub-directories?
>>>>
>>>> find . -type d -links 2
>>
>> This is indeed a very clever way to solve the issue. Kudos!
>>
>>>
>>> When I run that on cygwin it produces no output:
>>
>> It works for me also on Cygwin.
>
> $ mkdir tmp
> $ mkdir tmp/foo
>
> $ find tmp -type d -print0 | awk -v RS='\0' '{delete
> empty[gensub("/[^/]+$","",1)]; empty[$0]} END{for (dir in empty) print dir}'
> tmp/foo
>
> $ find tmp -type d -links 2
> $
>
> Maybe different versions of something? I'm running
>
> $ bash --version
> GNU bash, version 4.3.42(3)-release (x86_64-unknown-cygwin)
>
> $ find --version
> find (GNU findutils) 4.5.12
>
> on Windows 7.
>

I'm running find 4.4.0 (and bash 3.2, but that seems irrelevant).

What does ls -ld * show as link counts - it's the number in the
second column on my system - on your system? I see 1 for ordinary
(non-hardlinked) files, 2 for expty directories, 3+ for directories
that contain more directories, as it does on Unix. Or have you maybe
some special file-system mounted?

Janis

[...]


Ed Morton

unread,
Aug 26, 2016, 11:26:21 AM8/26/16
to
$ ls -ld tmp
drwxr-xr-x+ 1 Ed None 0 Aug 26 09:50 tmp

$ ls -ld tmp/foo
drwxr-xr-x+ 1 Ed None 0 Aug 26 09:50 tmp/foo

I ran it on all of the directories in my $HOME and that value is always 1.

> Or have you maybe some special file-system mounted?

Not as far as I know. I bought a laptop, installed cygwin and that's it.

Ed.

Barry Margolin

unread,
Aug 27, 2016, 3:50:43 AM8/27/16
to
In article <nppn2q$u4m$1...@dont-email.me>,
Apparently Cygwin doesn't emulate this detail of Unix filesystems.

--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***

Thomas 'PointedEars' Lahn

unread,
Aug 27, 2016, 5:44:52 AM8/27/16
to
Since that is apparently not so on all Unices, I suggest the following
approach instead.

1. To get all subdirectories in a directory "$dir", unsorted,
one level deep:

(cd "$dir" && find . \( -name . -o -prune \) -type d ! -name .)

2. To do (1) for all subdirectories in a directory "$dir", all levels:

find "$dir" \
-type d \
-exec sh -c '
find . \( -name . -o -prune \) -type d ! -name .
' sh '{}' ';'

3. To exclude from (2) those directories that contain subdirectories, and
print only them, with the current directory as fallback for the search
root [otherwise even GNU find(1) will complain if "$dir" has length 0]:

find "${dir:-.}" \
-type d \
-exec sh -c '(
cd "$1" &&
[ -z "$(find . \( -name . -o -prune \) -type d ! -name .)" ]
)' sh '{}' ';' \
-print

With GNU find, this can be simplified to

find "${dir:-.}" \
-type d \
-exec sh -c '(
cd "$1" &&
[ -z "$(find . -maxdepth 1 -type d ! -name .)" ]
)' sh '{}' ';' \
-print

[GNU find also has “-empty”, but for directories this applies only to
those that are truly *empty*, i.e. devoid of *all* files (except “.”
and “..”).]

WFM. Optimizations are welcome.

<http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html>

--
PointedEars

Twitter: @PointedEars2
Please do not cc me. / Bitte keine Kopien per E-Mail.

Thomas 'PointedEars' Lahn

unread,
Aug 27, 2016, 5:52:52 AM8/27/16
to
[Minor correction, and supplemental, to the superseded posting]

Casper H. S. Dik wrote:

Since that is apparently not so on all Unices, I suggest the following
approach instead.

1. To get all subdirectories in a directory "$dir", unsorted,
one level deep:

(cd "$dir" && find . \( -name . -o -prune \) -type d ! -name .)

2. To do (1) for all subdirectories in a directory "$dir", all levels:

find "$dir" \
-type d \
-exec sh -c '
cd "$1" &&
find . \( -name . -o -prune \) -type d ! -name .
' sh '{}' ';'

3. To exclude from (2) those directories that contain subdirectories, and
print only them, with the current directory as fallback for the search
root [otherwise even GNU find(1) will complain if "$dir" has length 0]:

find "${dir:-.}" \
-type d \
-exec sh -c '(
cd "$1" &&
[ -z "$(find . \( -name . -o -prune \) -type d ! -name .)" ]
)' sh '{}' ';' \
-print

With GNU find, this can be simplified to

find "${dir:-.}" \
-type d \
-exec sh -c '(
cd "$1" &&
[ -z "$(find . -maxdepth 1 -type d ! -name .)" ]
)' sh '{}' ';' \
-print

[GNU find also has “-empty”, but for directories this applies only to
those that are truly *empty*, i.e. devoid of *all* files (except “.”
and “..”).

If one also wants to consider those directories that cannot be changed
into, then “! -exec … [ -n … ]” is the solution.]

Janis Papanagnou

unread,
Aug 27, 2016, 7:51:11 AM8/27/16
to
On 27.08.2016 09:50, Barry Margolin wrote:
> In article <nppn2q$u4m$1...@dont-email.me>,
> Ed Morton <morto...@gmail.com> wrote:
>> On 8/26/2016 10:07 AM, Janis Papanagnou wrote:
>>> On 26.08.2016 16:55, Ed Morton wrote:
>>>> On 8/26/2016 9:23 AM, Janis Papanagnou wrote:
>>>>> On 26.08.2016 15:57, Ed Morton wrote:
>>>>>> On 8/26/2016 1:17 AM, Casper H.S. Dik wrote:
>>>>>>>
>>>>>>> find . -type d -links 2
>>>>>>
>>>>>> When I run that on cygwin it produces no output:
>>>>>
>>>>> It works for me also on Cygwin.
>>>>
>>>> Maybe different versions of something? I'm running [...] on Windows 7.
>>>
>>> [ link counts ]
>>
>> I ran it on all of the directories in my $HOME and that value is always 1.

Meanwhile I can reproduce that behaviour. In some tests (see below) I
see that hard-linked orinary files work, only the directory link count
is wrong.

>>
>>> Or have you maybe some special file-system mounted?
>>
>> Not as far as I know. I bought a laptop, installed cygwin and that's it.
>
> Apparently Cygwin doesn't emulate this detail of Unix filesystems.

As said, it does for my Cygwin installation. If it's an issue of specific
(maybe newer) Cygwin releases it would rather smell like a bug (presuming
the underlying file system supports it, of course).

I've just done some more tests on my laptop; I have two Cygwin terminal
icons that both open bash shells, one from an older Cygwin installation,
and one from a newer one. The newer one behaves as Ed described it, the
older one works as expected. - I guess this is one more data point of
annoying Cygwin behaviour. (Good that I have my native Linux system to
work with.) Someone who is more concerned about Cygwin than I am might
want examine that discrepancy further (or consider sending a bug report).

Janis

Cary

unread,
Aug 27, 2016, 4:33:11 PM8/27/16
to
Hi.
The script attached below the sig behaves like "find. -type d links 2" but
slower, of course. So far it has failed in one test case. That was a directory
with over 140500 files each having a filename 97 characters in lngth. Are there
any embedded systems out there which do not have find ?

--
ca...@sdf.org
SDF Public Access UNIX System - http://sdf.org



_____________________cut____________________________
#!/bin/bash

# script to list directories without subs
# uses builtin shell functions
#
shopt -s dotglob

function dir_ls { # print names

local name=

name="${1%/}" # find prints directories this way

printf "%s\n" "$name" # change format here

}

function chk_subd {

local DNUM=0 # initialize counter
local PREFIX=
local line=
local dir=

if [ -d "$1" ] ; then
PREFIX="$1"
else # first argument not a directory
exit 1
fi

for line in "${PREFIX}"/*; do # expand pattern to list top level files

if [ -d "$line" ] && [ ! -L "$line" ]; then

DNUM=$(( DNUM + 1 ))
chk_subd "${line}" # recurse into child directories

fi

done

# total subdirs have been counted for each directory
# but for this script we list only if there are no subs
if [[ $DNUM = 0 ]]; then # has no children
dir_ls "$PREFIX" # call print function
fi

}

if (( "$#" == 0 )) || (( "$#" > 1 )); then
printf " syntax: %s dirname\n" "$(basename $0)"
exit 1
fi

chk_subd "$@" # check pathname for subdirectories

Rakesh Sharma

unread,
Aug 28, 2016, 12:41:19 AM8/28/16
to
On Saturday, 27 August 2016 15:14:52 UTC+5:30, Thomas 'PointedEars' Lahn wrote:

>
> find "${dir:-.}" \
> -type d \
> -exec sh -c '(
> cd "$1" &&
> [ -z "$(find . \( -name . -o -prune \) -type d ! -name .)" ]
> )' sh '{}' ';' \
> -print
>
> With GNU find, this can be simplified to
>
> find "${dir:-.}" \
> -type d \
> -exec sh -c '(
> cd "$1" &&
> [ -z "$(find . -maxdepth 1 -type d ! -name .)" ]
> )' sh '{}' ';' \
> -print
>
> [GNU find also has “-empty”, but for directories this applies only to
> those that are truly *empty*, i.e. devoid of *all* files (except “.”
> and “..”).]
>
> WFM. Optimizations are welcome.
>


> find . \( -name . -o -prune \) -type d ! -name .

can be equivalently written as
find . ! -name . -prune -type d

Also this find will collect all the subdirs before stopping. While we can stop as soon
we determine if there's even one subdir.
Not sure if you looked at the solution I provided in this thread , but it stops as soo
as it finds the first subdir with the GNU find
-quit option.

The -empty option from GNU find can be used to bypass the -exec and print a successful match.

-exec sh -c 'code' sh '{}' ';'

is nonportable as shells process the first arg
differently.

-exec sh -c 'code' '{}' '{}' ';'
shall work durably.

Cary

unread,
Aug 28, 2016, 2:33:10 PM8/28/16
to
Cary wrote:
> James wrote:
>> How to list directories (in a tree structure) which have no sub-directories?
>>
>> TIA
>> James
>>
> Hi.
> The script attached below the sig behaves like "find. -type d links 2" but
> slower, of course. So far it has failed in one test case. That was a directory
> with over 140500 files each having a filename 97 characters in lngth. Are there
> any embedded systems out there which do not have find ?
>
Posting one last revision of the script. :\)
By default emulation of find is not changed. It has one option to count files
and children in all directories of the tree when a user specifies '-a' as as
argument.

--
ca...@sdf.org
SDF Public Access UNIX System - http://sdf.org

___________________cut_________________________

#!/bin/bash

# script to list directories without subs
# with option to report on all directories
# uses builtin shell functions
shopt -s dotglob

function dir_ls { # print names

local name=

if [[ $# = 1 ]]; then # default

name="${1%/}" # find prints directories this way
printf "%s\n" "$name" # change format here

else # script called with optional argument

local subs="$1" files="$2"
local sub="subdirectories"
local file="files"
name="${3%/}"

# don't use plural form
if [[ "$subs" = 1 ]]; then sub="subdirectory"; fi
if [[ "$files" = 1 ]]; then file="file"; fi

# prints three lines for each subdirectory
printf "%s %s\n" "$subs" "$sub" "$files" "$file" "$name"

fi

}

function chk_subd {

local DNUM=0 # initialize counter
local PREFIX=
local line=

if [[ $# = 2 ]] ; then # process optional argument
local FNUM=0 # initialize counter
shift
fi

if [ -d "$1" ] ; then
PREFIX="$1"
else # first argument not a directory
exit 1
fi

for line in "${PREFIX}"/*; do # expand pattern to list top level files

if [ ! $FNUM ]; then # find empty directories
if [ -d "$line" ] && [ ! -L "$line" ]; then

DNUM=$(( DNUM + 1 ))
chk_subd "${line}" # recurse into child directories

fi
else # finding contents of all subdirectories
if [ -d "$line" ] && [ ! -L "$line" ]; then

DNUM=$(( DNUM + 1 ))
chk_subd shift "${line}" # call with extra parameter

elif [ -f "$line" ] ; then

FNUM=$(( FNUM + 1 )) # count 1 file

fi
fi

done

# total subdirs have been counted for each directory
# default for this script to list only if there are no subs
if [[ $DNUM = 0 ]] && [ ! $FNUM ]; then # has no children

dir_ls "$PREFIX" # call print function

elif [ $FNUM ]; then # option to list all directories

dir_ls "$DNUM" "$FNUM" "$PREFIX"
printf "\n"

fi

}

USAGE="[-a] .|path-to-dir"
SH_NAME="$(basename $0)"

case $# in
1) : ;;
2)
# the option "-a" will make script print
# information about all directories
if [[ $1 != "-a" ]]; then # exception
printf "Usage: %s %s\n" "$SH_NAME" "$USAGE"
exit 1
fi
;;
*)
printf "Usage: %s %s\n" "$SH_NAME" "$USAGE"
;;
esac

Rakesh Sharma

unread,
Aug 28, 2016, 11:35:33 PM8/28/16
to
On Monday, 29 August 2016 00:03:10 UTC+5:30, Cary wrote:
> Cary wrote:
> > James wrote:
> >> How to list directories (in a tree structure) which have no sub-directories?
> >>
> >> TIA
> >> James
> >>
> > Hi.
> > The script attached below the sig behaves like "find. -type d links 2" but
> > slower, of course. So far it has failed in one test case. That was a directory
> > with over 140500 files each having a filename 97 characters in lngth. Are there
> > any embedded systems out there which do not have find ?
> >
> Posting one last revision of the script. :\)
> By default emulation of find is not changed. It has one option to count files
> and children in all directories of the tree when a user specifies '-a' as as
> argument.
>
> -->__________________cut_________________________
How will your bash code hold up in case of a large number of files/subdirs in a particular dir? Especially the below line..

Janis Papanagnou

unread,
Aug 28, 2016, 11:55:43 PM8/28/16
to
What are you expecting? - The larger the number of files the more time it
will require for processing.

In this context there's no exec buffer limit, if that's what you have in
mind.

If you mean something different then express it explicitly.

Janis

Cary

unread,
Aug 29, 2016, 12:48:11 PM8/29/16
to
Hi, Rakesh.

Please note above that I said the script had failed in one particular
case. Yes. There was a limit I hit which would not be a problem for
find. The glob can expand to a string that is too much to parse. Then
the script hangs and the process has to be stopped. On hardware with
less memory/cpu it could be even more of a problem.
OTOH in a directory of css and js sources with around 4500 child
directories the script was not too bad. 1.583 seconds to find
2507 empty directories. find needed .093 s to print the same number.

Thomas 'PointedEars' Lahn

unread,
Sep 2, 2016, 2:05:58 AM9/2/16
to
Rakesh Sharma wrote:

> On Saturday, 27 August 2016 15:14:52 UTC+5:30, Thomas 'PointedEars' Lahn
> wrote:
>> find . \( -name . -o -prune \) -type d ! -name .
>
> can be equivalently written as
> find . ! -name . -prune -type d

Are you sure?

> Also this find will collect all the subdirs before stopping. While we can
> stop as soon we determine if there's even one subdir.
> Not sure if you looked at the solution I provided in this thread ,

I have not yet. You are posting via the spammed, trolled, Google Groups; I
have a filter which hides those postings unless they are follow-ups to mine.

> but it stops as soo as it finds the first subdir with the GNU find
> -quit option.

Yes, but that only works with *GNU* find.

> The -empty option from GNU find can be used to bypass the -exec and print
> a successful match.

AISB, GNU find’s “-empty” does not solve this particular problem.

> -exec sh -c 'code' sh '{}' ';'
>
> is nonportable as shells process the first arg
> differently.

Which shells that can be invoked with “sh” do not consider, with the “-c”
option specified, the first positional argument the name of the shell?

> -exec sh -c 'code' '{}' '{}' ';'
> shall work durably.

How so? With '{}', the name of the found file is passed as the value of
that argument, which with GNU bash makes it “the name of the shell, which is
used in warning and error messages”. What purpose is this supposed to
serve?

Geoff Clare

unread,
Sep 2, 2016, 9:41:07 AM9/2/16
to
Thomas 'PointedEars' Lahn wrote:

> Rakesh Sharma wrote:
>
>> -exec sh -c 'code' sh '{}' ';'
>>
>> is nonportable as shells process the first arg
>> differently.
>
> Which shells that can be invoked with “sh” do not consider, with the “-c”
> option specified, the first positional argument the name of the shell?

/bin/sh on BSD systems released more than about 20 years ago.
(It was changed to conform to POSIX in FreeBSD/NetBSD/etc. in
the mid-1990's.)

--
Geoff Clare <net...@gclare.org.uk>

Sven Mascheck

unread,
Sep 2, 2016, 6:40:09 PM9/2/16
to
Geoff Clare wrote:
> /bin/sh on BSD systems released more than about 20 years ago.
> (It was changed to conform to POSIX in FreeBSD/NetBSD/etc. in
> the mid-1990's.)

- that is, early releases of the Almquist shell
(e.g. FreeBSD < 2.1.0, NetBSD < 1.2, Minix < 3.1.3,
and the traditional BSD of course)

- and also ksh88 before release f.

quite old stuff really, but interesting to know.
At best, usually relevant for maximum portable install scripts only.
If you "accidentally" call ksh instead of sh (a POSIX ksh) on HP-UX,
then you get such an old ksh88(c), though.

http://www.in-ulm.de/~mascheck/various/find/#shell

Rakesh Sharma

unread,
Sep 3, 2016, 7:51:37 AM9/3/16
to
On Friday, 2 September 2016 11:35:58 UTC+5:30, Thomas 'PointedEars' Lahn wrote:

>
> > On Saturday, 27 August 2016 15:14:52 UTC+5:30, Thomas 'PointedEars' Lahn
> > wrote:
> >> find . \( -name . -o -prune \) -type d ! -name .
> >
> > can be equivalently written as
> > find . ! -name . -prune -type d
>
> Are you sure?
>

Yes, very sure. Boolean algebra makes that happen.

-name . ==> A
-prune ==> B
-type d ==> C
-o => + (the boolean OR operator)
! -name . ==> not A == A_bar

Then the find statement: find . \( -name . -o -prune \) -type d ! -name .
can be recast as: find . \( A + B \) C A_bar
(A + B). C . A_bar
= ( A . A_bar + B . A_bar ) . C
= ( 0 + A_bar . B ) . C
= A_bar . B . C

Hence,
find . ! -name . -prune -type d
Q.E.D.

>
> > The -empty option from GNU find can be used to bypass the -exec and print
> > a successful match.
>
> AISB, GNU find’s “-empty” does not solve this particular problem.
>
What I meant was that -empty option can short circuit the evaluation of a true
result, like as shown below

find . -type d \( -empty -o -exec sh -c '
cd "$1" && \
case $(find . -maxdepth 1 -type d ! -name . -print -quit) in
"") :;;
* ) false;;
esac
' {} {} \; \) -print


> > -exec sh -c 'code' sh '{}' ';'
> >
> > is nonportable as shells process the first arg
> > differently.
>
> Which shells that can be invoked with “sh” do not consider, with the “-c”
> option specified, the first positional argument the name of the shell?
>
> > -exec sh -c 'code' '{}' '{}' ';'
> > shall work durably.
>
> How so? With '{}', the name of the found file is passed as the value of
> that argument, which with GNU bash makes it “the name of the shell, which is
> used in warning and error messages”. What purpose is this supposed to
> serve?
>
> --
> PointedEars
>

Consult this site for more info.
http://www.in-ulm.de/~mascheck/various/find/#shell

Michael Paoli

unread,
Sep 4, 2016, 1:22:40 PM9/4/16
to
I was thinking that *would* be a slick answer ... *except*,
unfortunately it's not true for all file system types.
But it does work fine for the more ordinary/classic types of
Unix/Linux/BSD filesystems.

lawren...@gmail.com

unread,
Sep 15, 2016, 3:58:18 AM9/15/16
to
On Monday, September 5, 2016 at 5:22:40 AM UTC+12, Michael Paoli wrote:
>
> On Thursday, August 25, 2016 at 11:17:21 PM UTC-7, Casper H.S. Dik wrote:
>
>> (A directory has at least two links; and one additional for each sub
>> directory)
>
> I was thinking that *would* be a slick answer ... *except*,
> unfortunately it's not true for all file system types.

It shouldn’t be true for *any* filesystem types. The “.” and “..” links in directories serve no purpose except to get in the way, and should be removed.

Janis Papanagnou

unread,
Sep 15, 2016, 4:25:23 AM9/15/16
to
Both serve me well on a daily basis and for decades. Frankly, it's been quite
some time that I've heard (or read) such nonsense. So I wonder; where did you
get that from?

Janis

Kenny McCormack

unread,
Sep 15, 2016, 5:00:51 AM9/15/16
to
In article <nrdltf$a61$1...@news-1.m-online.net>,
Janis Papanagnou <janis_pa...@hotmail.com> wrote:
...
>Both serve me well on a daily basis and for decades. Frankly, it's been quite
>some time that I've heard (or read) such nonsense. So I wonder; where did you
>get that from?

Come on, this is Usenet!

You read such nonsense here every day.
That's why you keep coming back for more...

--
http://www.rollingstone.com/politics/news/the-10-dumbest-things-ever-said-about-global-warming-20130619

lawren...@gmail.com

unread,
Sep 15, 2016, 5:06:20 AM9/15/16
to
On Thursday, September 15, 2016 at 8:25:23 PM UTC+12, Janis Papanagnou wrote:
> Frankly, it's been quite
> some time that I've heard (or read) such nonsense. So I wonder; where did you
> get that from?

Ask a civil question, and you might get a civil response.

Ask a stupid question, on the other hand...

Kaz Kylheku

unread,
Sep 15, 2016, 10:10:03 AM9/15/16
to
The . link serves no purpose, certainly.

If you were designing an in-memory n-ary tree data structure, you
wouldn't include a structure member which just points back to the
same structure. Just parent and child links.

The parent link is necessary so that you can navigate to the parent
if all you have is a reference to the child.

There is no way to resolve a relative path containing an unbalanced
"..", such as one which begins with that component.

I suppose it could be handled using in-memory data structures. That is
to say, the kernel could resolve ".." by keeping in-memory inode
references. A process could have a CWD, and that CWD object could have
is PWD (in memory only) and so on up to the root. Then ".." would be
pure syntax, not referring to any entry in the file system, the way the
separating slash is just syntax.

Kaz Kylheku

unread,
Sep 15, 2016, 10:15:42 AM9/15/16
to
Well, the path *syntax* serves well!

Lawrence didn't say, 'let us break ./cmd-not-in-path and
#include "../up/down/inc/foo.h"'.

The . and .. syntax can work without there being corresponding entries
on disk.

Directories could, by the way, have the same inode refcount without the
links. That is to say, a directory containing nothing but four
subdirectories can still have a link count of four, even if those
children do not have explicit ".." entries.

Janis Papanagnou

unread,
Sep 15, 2016, 11:38:24 AM9/15/16
to
On 15.09.2016 11:00, Kenny McCormack wrote:
> In article <nrdltf$a61$1...@news-1.m-online.net>,
> Janis Papanagnou <janis_pa...@hotmail.com> wrote:
> ...
>> Both serve me well on a daily basis and for decades. Frankly, it's been quite
>> some time that I've heard (or read) such nonsense. So I wonder; where did you
>> get that from?
>
> Come on, this is Usenet!
>
> You read such nonsense here every day.
> That's why you keep coming back for more...

Nonsense, yes. But it's been some time that I read such utter nonsense here.

Janis

Janis Papanagnou

unread,
Sep 15, 2016, 11:44:16 AM9/15/16
to
On 15.09.2016 16:15, Kaz Kylheku wrote:
[...]
>
> The . and .. syntax can work without there being corresponding entries
> on disk.

You can include such inconsistencies but there's no need to do.

Janis

Janis Papanagnou

unread,
Sep 15, 2016, 11:50:55 AM9/15/16
to
Since you didn't notice; it was a rhetoric question. A "stupid" thesis
(as you'd call it, or nonsensical as I would), specifically without any
rationale, has no substance to discuss anyway. Kenny is right, Usenet
(and the web) is full of such stuff.

Janis

Michael Paoli

unread,
Sep 15, 2016, 9:02:35 PM9/15/16
to
On Thursday, August 25, 2016 at 4:07:30 PM UTC-7, James wrote:
> How to list directories (in a tree structure) which have no sub-directories?

Could do something approximately like this (could use temporary files,
or store in a named parameter, in any case ...)

find . -type d -print | sort > directories
<directories sed -ne 's/\/[^\/][^\/]*$//p' | sort -u > parents
comm -23 directories parents

Note that the above won't work in all cases ...
e.g. if one has directory pathnames that contain newline
character(s).

Anyway, that's about the simplest (and most reasonably efficient)
*shell* based solution I can think of, though, as noted, doesn't cover
100% of all cases. I can certainly think of ways to do it 100%
in perl - and including cases of newlines present in directories.
Of course then one would need to specify some record delimiter or
terminator that couldn't be used in directory pathname ... which
pretty much would just leave ASCII null for that role.

Anyway, perl (or I presume likewise python, etc.) could do it 100%,
but I think most any other solutions, though quite possibly be 100%,
will be more than about 3 lines 'o code ... so ... 3 lines 'o
shell code ain't bad for a quite concise and close to, but not
quite 100% solution. If directories aren't being added/dropped while
this is being done, one could of course look for directories that
contain newline in the name, e.g.:
find . -type d -name '*
*' -print

Michael Paoli

unread,
Sep 15, 2016, 11:14:38 PM9/15/16
to
P.S.
Could also potentially use fgrep -xv rather than comm,
and possibly reduce the sort overhead a bit .... but the fgrep
computation work may be more than that of comm, so may or may
not be a net processing efficiency gain.

Michael Paoli

unread,
Sep 16, 2016, 6:05:52 AM9/16/16
to
Or ... a 100% perl solution,
directories output terminated by ASCII null:

#!/usr/bin/perl
$^W=1;
use strict;
use File::Find;

my $parentdir=undef;

sub process {
if(-d _){
if(!defined($parentdir)){
print($File::Find::name,"\0",);
}else{
print($File::Find::name,"\0",) if $parentdir ne $File::Find::name;
};
$parentdir=$File::Find::dir;
};
};

for my $arg (@ARGV){
$parentdir=undef;
find({ wanted => \&process, bydepth => 1, }, ($arg));
};

And, bit of explanation on how that works.
First of all, perl's File::Find has a capability rather like
find(1)'s -depth option, by setting its bydepth option to 1 or using
finddepth. Essentially by using such, it will always process the
contents of a directory before processing the directory itself.
Then, we also make some logical presumptions about how perl's File::Find
goes about implementing that. It's not merely processing contents of
directory before processing directory itself, but is a bit more strict
than that. Let's say we have a directory structure like this - where
all named path elements are of type directory:
d0/d1
d0/d2/d3
d0/d2/d4
We can very reasonably presume, that not only will it process d3 and d4
before processing d2, but also that it won't process d1 between
processing d3 and d4, and processing d2. Why? Recursion, "of course".
How does one think its implemented in perl? It has to read the
contents of a directory to determine if it has any subdirectories
within, and if so, it must descend and process those first. It would
"of course" use recursion on that, and eventually backtrack. Hence we
have somewhat more predictability of the order of processing of
directories beyond it merely processing entries in directory before
processing directory itself. Notably that for a given ancestor
directory, all descendants will be processed before that ancestor, and
for any directory which is not that ancestor or descendant thereof, it
will either be processed before that ancestor and its descendants are
processed, or it will be processed after that ancestor and its
descendants are processed. We can further logically break that down.
The first directory we process must be a "leaf" directory - because any
descendant directories would be processed earlier. We track the
immediate parent of this leaf. Next time we're processing a directory,
we check that it's not same as parent we tracked with our last
directory visit. If it matches, it's a parent and we don't output it,
if it doesn't match, it must be a leaf directory - we know this due to
the restrictions on the ordering used in the recursion to process the
directories - so we only have to track that one bit of state - perl
tracks everything else needed inherently in the recursion of doing
depth first processing. That's basically it. And doing this in perl,
we have much efficiency, by avoiding need for any fork(2) or exec(3)
calls or the like, we avoid any need to sort and related storage, and
we only need track pretty minimal state information - other than that
which perl tracks for us in its recursion - which is close to what
minimally would need to be tracked anyway.
One other key bit here too, use of _ - that's effectively a "magic"
filehandle in perl - we can lstat it to check if it's a directory. But
what file does it refer to? The last on which an lstat was done. And,
due to how we can reasonably presume File::Find to work, we can safely
presume that when we go to examine _, it's already cached lstat data
for the file (of whatever type) we're about to process. So, using -d _
allows us to test against the existing cached lstat data (to check if
it's a directory), without need to do another actual lstat(2) system
call. Anyway, we're able to leverage those bits of how File::Find
works in perl to make our task at hand particularly efficient ... and
if we weren't certain about those behaviors, we could examine the
source code ... but those are very reasonable presumptions based upon
how perl works in general, and what File::Find needs to do, and how it
would implement it reasonably efficiently, and particularly also how it
would need to reasonably efficiently implement its bydepth/finddepth
functionality.

Geoff Clare

unread,
Sep 16, 2016, 9:11:06 AM9/16/16
to
Janis Papanagnou wrote:

> On 15.09.2016 16:15, Kaz Kylheku wrote:
> [...]
>>
>> The . and .. syntax can work without there being corresponding entries
>> on disk.
>
> You can include such inconsistencies but there's no need to do.

I actually think it would be better than what we have now. If UNIX
had been designed that way originally, I suspect we would now be
ridiculing file systems that have . and .. entries in directories:

"When you write a loop on readdir() you have to check for entries
where d_name is . or .. and skip them - ridiculous!"

"If you want a glob pattern that matches all filenames beginning with
a dot you can't just use .* but have to use .[!.]* ..?* - ridiculous!"

etc.

--
Geoff Clare <net...@gclare.org.uk>

Andy Walker

unread,
Sep 16, 2016, 10:40:55 AM9/16/16
to
On 16/09/16 13:55, Geoff Clare wrote:
> "If you want a glob pattern that matches all filenames beginning with
> a dot you can't just use .* but have to use .[!.]* ..?* - ridiculous!"

Whoa! You seem to be asking not for "all filenames beginning
with a dot" but for all of them bar two exceptions. It's not surprising
that that gets slightly complicated, just as it would if you asked for
all filenames other than "geoff" or "clare".

--
Andy Walker,
Nottingham.

Janis Papanagnou

unread,
Sep 16, 2016, 7:42:39 PM9/16/16
to
On 16.09.2016 14:55, Geoff Clare wrote:
> Janis Papanagnou wrote:
>
>> On 15.09.2016 16:15, Kaz Kylheku wrote:
>> [...]
>>>
>>> The . and .. syntax can work without there being corresponding entries
>>> on disk.
>>
>> You can include such inconsistencies but there's no need to do.
>
> I actually think it would be better than what we have now. If UNIX
> had been designed that way originally, I suspect we would now be
> ridiculing file systems that have . and .. entries in directories:
>
> "When you write a loop on readdir() you have to check for entries
> where d_name is . or .. and skip them - ridiculous!"

What's so ridiculous about that? If you use a function that accesses
files (all sorts of files incl. directories) you have to distinguish
the files you want to access anyway, if only by type.[*]

(And the other way round; if we wouldn't have .. and . we'd need to
implement a special (separate, "syntax" based as I think Kaz called it)
layer on top for the given parent and self reference, respectively.
I actually think this would be worse.)

>
> "If you want a glob pattern that matches all filenames beginning with
> a dot you can't just use .* but have to use .[!.]* ..?* - ridiculous!"

The hidden files concept (with the special hidden files . and ..) and
shell's globbing are issues of their own. This is not related to the
issue of 'links' in the file system (that [somehow] "get in the way").

Janis

[*] "UNIX is so ridiculous, all sort of files, ordinary, special files
like FIFOs, directories, etc., are all accessed the same way; but there
are still differences you have to know about, say no seek on pipes." -
made up questions like those don't lead anywhere. Every unified access
on entities that are not actually the exact same type will require at
some point a differentiation; the question is whether the unification
has provided some conceptual gain and generally easier consistent use.

etc.

>
> etc.
>

Kaz Kylheku

unread,
Sep 16, 2016, 8:01:28 PM9/16/16
to
On 2016-09-16, Janis Papanagnou <janis_pa...@hotmail.com> wrote:
> On 16.09.2016 14:55, Geoff Clare wrote:
>> Janis Papanagnou wrote:
>>
>>> On 15.09.2016 16:15, Kaz Kylheku wrote:
>>> [...]
>>>>
>>>> The . and .. syntax can work without there being corresponding entries
>>>> on disk.
>>>
>>> You can include such inconsistencies but there's no need to do.
>>
>> I actually think it would be better than what we have now. If UNIX
>> had been designed that way originally, I suspect we would now be
>> ridiculing file systems that have . and .. entries in directories:
>>
>> "When you write a loop on readdir() you have to check for entries
>> where d_name is . or .. and skip them - ridiculous!"
>
> What's so ridiculous about that? If you use a function that accesses
> files (all sorts of files incl. directories) you have to distinguish
> the files you want to access anyway, if only by type.[*]
>
> (And the other way round; if we wouldn't have .. and . we'd need to
> implement a special (separate, "syntax" based as I think Kaz called it)
> layer on top for the given parent and self reference, respectively.
> I actually think this would be worse.)

Actually we have something even worse: a hodge-podge of both. Sometimes
the kernel earnestly resolves .. through the directory structure, and
sometimes layers of string-based path munging code in user space treat
it as syntax, which cancels a previous path component.

Eric

unread,
Sep 17, 2016, 1:40:05 PM9/17/16
to
On 2016-09-17, Kaz Kylheku <221-50...@kylheku.com> wrote:
8>< --------
> Actually we have something even worse: a hodge-podge of both. Sometimes
> the kernel earnestly resolves .. through the directory structure, and
> sometimes layers of string-based path munging code in user space treat
> it as syntax, which cancels a previous path component.

The latter shouldn't be happening, except to produce a canonical form of
a file name (no . , no .. , no //) for display, which shuld be a single
library module somewhere.

[footnote] .. should be kept in the canonical form at the start of a
relative path.

Eric
--
ms fnd in a lbry

Geoff Clare

unread,
Sep 19, 2016, 8:41:07 AM9/19/16
to
Janis Papanagnou wrote:

> On 16.09.2016 14:55, Geoff Clare wrote:
>> Janis Papanagnou wrote:
>>
>>> On 15.09.2016 16:15, Kaz Kylheku wrote:
>>> [...]
>>>>
>>>> The . and .. syntax can work without there being corresponding entries
>>>> on disk.
>>>
>>> You can include such inconsistencies but there's no need to do.
>>
>> I actually think it would be better than what we have now. If UNIX
>> had been designed that way originally, I suspect we would now be
>> ridiculing file systems that have . and .. entries in directories:
>>
>> "When you write a loop on readdir() you have to check for entries
>> where d_name is . or .. and skip them - ridiculous!"
>
> What's so ridiculous about that? If you use a function that accesses
> files (all sorts of files incl. directories) you have to distinguish
> the files you want to access anyway, if only by type.[*]

Maybe saying "ridiculous" was a bit strong. (I mainly used it because
it matched the earlier "we would now be ridiculing".)

I think most programmers would prefer it if they didn't have to do
the check for "." and ".." on every readdir() loop they write.

> (And the other way round; if we wouldn't have .. and . we'd need to
> implement a special (separate, "syntax" based as I think Kaz called it)
> layer on top for the given parent and self reference, respectively.
> I actually think this would be worse.)

The kernel would just have special handling for "." and ".." that makes
them work in pathnames exactly as they do now. For users/programmers
the only noticeable difference would be that readdir() does not return
directory entries for "." and ".." (and the knock-on effects of that
for shell globbing, etc.)

>> "If you want a glob pattern that matches all filenames beginning with
>> a dot you can't just use .* but have to use .[!.]* ..?* - ridiculous!"
>
> The hidden files concept (with the special hidden files . and ..) and
> shell's globbing are issues of their own. This is not related to the
> issue of 'links' in the file system (that [somehow] "get in the way").

Seems like the same issue as readdir() to me. After all, the shell is
calling readdir() (or getdents() or similar) to get the list of files
in the current directory to match against the pattern. The hidden
files concept would be cleaner, and easier to make use of, if directory
entries for "." and ".." did not exist.

--
Geoff Clare <net...@gclare.org.uk>

Kaz Kylheku

unread,
Sep 19, 2016, 10:28:56 AM9/19/16
to
On 2016-09-19, Geoff Clare <ge...@clare.See-My-Signature.invalid> wrote:
> Janis Papanagnou wrote:
>
>> On 16.09.2016 14:55, Geoff Clare wrote:
>>> Janis Papanagnou wrote:
>>>
>>>> On 15.09.2016 16:15, Kaz Kylheku wrote:
>>>> [...]
>>>>>
>>>>> The . and .. syntax can work without there being corresponding entries
>>>>> on disk.
>>>>
>>>> You can include such inconsistencies but there's no need to do.
>>>
>>> I actually think it would be better than what we have now. If UNIX
>>> had been designed that way originally, I suspect we would now be
>>> ridiculing file systems that have . and .. entries in directories:
>>>
>>> "When you write a loop on readdir() you have to check for entries
>>> where d_name is . or .. and skip them - ridiculous!"
>>
>> What's so ridiculous about that? If you use a function that accesses
>> files (all sorts of files incl. directories) you have to distinguish
>> the files you want to access anyway, if only by type.[*]
>
> Maybe saying "ridiculous" was a bit strong. (I mainly used it because
> it matched the earlier "we would now be ridiculing".)
>
> I think most programmers would prefer it if they didn't have to do
> the check for "." and ".." on every readdir() loop they write.

There is also efficiency. Think about:

rename("x/a/dir", "y/b/dir");

of course, this is changing the content of a and b, so they have to be
edited. But since dir has a silly parent link, it too must be updated,
resulting in extra I/O. Without the parent link, not so much as even the
atime of dir has to change.

Michael Paoli

unread,
Sep 23, 2016, 1:43:32 AM9/23/16
to
Those . and .. entries are exceedingly useful and handy. With ..
one always has a consistent way to get to the parent directory,
and with both .. and ., very handy and consistent ways to always be
able to reference the parent and current directories - e.g. to get
information about them, reference information in them, etc.

Kenny McCormack

unread,
Sep 23, 2016, 3:12:19 AM9/23/16
to
In article <2a331d14-f4fd-4f70...@googlegroups.com>,
I think the implication here is that the kernel could be jiggered to act
like . and .. exist and work with the usual semantics, even though they
don't physically exist. Since they don't physically exist, they wouldn't
be returned by the various functionalities that itemize directory contents,
but they would still work as expected in all of the cases to which you
allude.

It works for me!

--
Modern Conservative: Someone who can take time out from flashing her
wedding ring around and bragging about her honeymoon to complain that a
fellow secretary who keeps a picture of her girlfriend on her desk is
"flauting her sexuality" and "forcing her lifestyle down our throats".

Casper H.S. Dik

unread,
Sep 23, 2016, 3:42:10 AM9/23/16
to
Michael Paoli <micha...@yahoo.com> writes:

>Those . and .. entries are exceedingly useful and handy. With ..
>one always has a consistent way to get to the parent directory,
>and with both .. and ., very handy and consistent ways to always be
>able to reference the parent and current directories - e.g. to get
>information about them, reference information in them, etc.

But there was never a need for them to be part of directory
itself. They could all be hidden.

Casper

Janis Papanagnou

unread,
Sep 23, 2016, 5:18:49 AM9/23/16
to
Say, you want to traverse directories from current working directory
to root directory (say, to collect permissions); isn't it straight
forward to rely on '..' in chdir(2), or how would you solve that
without fiddling with absolute paths (inclusive symlinked directories)
and string manipulation?

As I see it, the whole point in introducing '..' and '.' is to have a
consistent directory model, and to navigate not only with absolute but
also with relative paths without a global context or decoupled (thus
error prone) logical layers.

Janis

>
> Casper
>

Kaz Kylheku

unread,
Sep 23, 2016, 11:07:31 AM9/23/16
to
On 2016-09-23, Michael Paoli <micha...@yahoo.com> wrote:
> On Thursday, September 15, 2016 at 12:58:18 AM UTC-7, lawren...@gmail.com wrote:
>> On Monday, September 5, 2016 at 5:22:40 AM UTC+12, Michael Paoli wrote:
>> >
>> > On Thursday, August 25, 2016 at 11:17:21 PM UTC-7, Casper H.S. Dik wrote:
>> >
>> >> (A directory has at least two links; and one additional for each sub
>> >> directory)
>> >
>> > I was thinking that *would* be a slick answer ... *except*,
>> > unfortunately it's not true for all file system types.
>>
>> It shouldn’t be true for *any* filesystem types. The “.” and “..”
>> links in directories serve no purpose except to get in the way, and
>> should be removed.
>
> Those . and .. entries are exceedingly useful and handy. With ..

What situation do they exceed?

> one always has a consistent way to get to the parent directory,

Though already noted several times in the thread, parent navigation
can be provided by supporting .. as a syntax in paths in the
kernel function that resolves paths; it doesn't require a parent
pointer explicitly represented on disk.

> and with both .. and ., very handy and consistent ways to always be

Nobody said anything about removing these from path syntax,
which would be insane.

Kaz Kylheku

unread,
Sep 23, 2016, 11:20:35 AM9/23/16
to
On 2016-09-23, Janis Papanagnou <janis_pa...@hotmail.com> wrote:
> On 23.09.2016 09:41, Casper H.S. Dik wrote:
>> Michael Paoli <micha...@yahoo.com> writes:
>>
>>> Those . and .. entries are exceedingly useful and handy. With ..
>>> one always has a consistent way to get to the parent directory,
>>> and with both .. and ., very handy and consistent ways to always be
>>> able to reference the parent and current directories - e.g. to get
>>> information about them, reference information in them, etc.
>>
>> But there was never a need for them to be part of directory
>> itself. They could all be hidden.
>
> Say, you want to traverse directories from current working directory
> to root directory (say, to collect permissions); isn't it straight
> forward to rely on '..' in chdir(2), or how would you solve that
> without fiddling with absolute paths (inclusive symlinked directories)
> and string manipulation?

The "current working directory" is an *object* in the kernel associated
with the calling process. That object can have a parent pointer,
serving the role of "..". It's just that this parent pointer doesn't
exist in the external version of the directory on disk.

What the persistent ".." allows us to do is to throw away these
in-kernel parent objects to save memory; we can pick them up from
disk. This is probably why it was done that way; it's an old design
decision that harkens back to the days when /vmunix was less than fifty
kilobytes and memories were tiny.

> As I see it, the whole point in introducing '..' and '.' is to have a
> consistent directory model, and to navigate not only with absolute but

Parent pointers needed in tree structures for the sake of consistency is
big news to me.

A parent or self pointer which is not there cannot be inconsistent. It
only adds an extra burden. Fsck has to check that if C is a child of P,
then C has a .. entry, and that entry corresponds to P.
Without parent pointers, no such consistency check is required.
Similarly, . entries have to be checked that they sanely point back
to the directory inode in whose data they are contained. If they
didn't exist, no inconsistency could exist in this regard.

Cycles could exist either way; .. doesn't prevent corruption that
causes cycles.

Eric

unread,
Sep 23, 2016, 12:40:05 PM9/23/16
to
On 2016-09-23, Kaz Kylheku <221-50...@kylheku.com> wrote:
> On 2016-09-23, Janis Papanagnou <janis_pa...@hotmail.com> wrote:
8>< --------
>>
>> Say, you want to traverse directories from current working directory
>> to root directory (say, to collect permissions); isn't it straight
>> forward to rely on '..' in chdir(2), or how would you solve that
>> without fiddling with absolute paths (inclusive symlinked directories)
>> and string manipulation?
>
> The "current working directory" is an *object* in the kernel associated
> with the calling process. That object can have a parent pointer,
> serving the role of "..". It's just that this parent pointer doesn't
> exist in the external version of the directory on disk.

Nonsense, it has nothing to do with "current working directory". ".."
can appear in the middle of a pathname not at the point of the CWD,
perhaps constructed from bits the current script knows nothing about. I
did this yesterday, it just came naturally, I didn't even think of this
thread at the time. CWD is only relevant when the "." or ".." is at the
beginning of a path.
>
> What the persistent ".." allows us to do is to throw away these
> in-kernel parent objects to save memory; we can pick them up from
> disk. This is probably why it was done that way; it's an old design
> decision that harkens back to the days when /vmunix was less than fifty
> kilobytes and memories were tiny.

I doubt it. Saving memory, yes, and processor cycles, because you don't
need your own path manipulation, and the kernel code doesn't need to
know what "." and ".." mean. No pointers to throw away because no-one
had ever thought of them.

8>< --------

Kaz Kylheku

unread,
Sep 23, 2016, 2:56:45 PM9/23/16
to
On 2016-09-23, Eric <er...@deptj.eu> wrote:
> On 2016-09-23, Kaz Kylheku <221-50...@kylheku.com> wrote:
>> On 2016-09-23, Janis Papanagnou <janis_pa...@hotmail.com> wrote:
> 8>< --------
>>>
>>> Say, you want to traverse directories from current working directory
>>> to root directory (say, to collect permissions); isn't it straight
>>> forward to rely on '..' in chdir(2), or how would you solve that
>>> without fiddling with absolute paths (inclusive symlinked directories)
>>> and string manipulation?
>>
>> The "current working directory" is an *object* in the kernel associated
>> with the calling process. That object can have a parent pointer,
>> serving the role of "..". It's just that this parent pointer doesn't
>> exist in the external version of the directory on disk.
>
> Nonsense, it has nothing to do with "current working directory". ".."

I do not speak nonsense, thanks. Yes, it does have to do with the
current working directory when .. is the first path component
in a relative path.

I interpreted Janis' comment as asking, how do we resolve an
out-of-the-blue chdir("..") without a ".." link.

> can appear in the middle of a pathname not at the point of the CWD,

Processing a pathname never *begins* in the middle. If .. appears
in the middle, then of course it proceeds from the directory that was
resolved thorugh the previous component.

If it occurs in the middle of a path, like "[...]/dir/..", that
problem Janis' is talking about doesn't exist, because obviously
we just resolved "dir", and so ".." refers to its parent.

We know what the parent is because ... drumroll ... under
this representational scheme we know what the parent is of every dir
object in memory, up through to root, and we do delete any
such object away if it is referenced by a child as its parent.
Every process' cwd and root pointers reference a complete chain
of dir objects leading up to root. Resolution of a relative path
begins at some process' cwd. Resolution of an absolute path begins at
some process' root.

> perhaps constructed from bits the current script knows nothing about. I
> did this yesterday, it just came naturally, I didn't even think of this
> thread at the time. CWD is only relevant when the "." or ".." is at the
> beginning of a path.

Thank you, Ein-fucking-stein.

Michael Paoli

unread,
Sep 23, 2016, 11:47:37 PM9/23/16
to
And, e.g. when you want to know information about current and/or parent directory. E.g. what device the filesystem is on, is the parent on the same device, or is it a different filesystem, what are the permissions of the
current and parent directories, what's the parent directory of some other
arbitrary directory pathname, yes .. and . come in exceedingly handy.

How else are you, e.g., going to lstat(2) the current and parent
directory?

Kaz Kylheku

unread,
Sep 24, 2016, 12:17:58 AM9/24/16
to
I believe I explaind that already.

The leftmost argument of lstat is a "const char *path". lstat has to
resolve this path (using what is called the "namei" routine in classic
Unix).

That routine can recognize .. and . as special syntax.

The input path falls into two cases:

1) relative
- when resolving this, we initialize a "dir" variable to
current_process->cwd.

2) absolute
- when resolving this, we initialize a "dir" variable to
current_process->root

(Root is either the absolute filesystem root dir, or else
the root of the chroot jail of the process.)

These These properties (cwd and root) of the current process are all
kernel objects of some sort. These objects can have some sort of
navigable references to the parents (if they have parents).

There is no such thing as resolving a path out of the blue in
the middle of the file system somewhere with no context object
from which to begin.

Say that the path is "./../abc". Since it doesn't begin with a slash,
it is case (1). So we initialize:

dir = current_process->cwd.

we extract the first component, which is ".". Aha, this is special
syntax! When we see "." we do this:

/* noop: dir = dir; */

Next, we parse out the component "..". Aha, another special case.
When we see this, we do:

dir = dir->parent;

If dir is now NULL, we bail out with an error (the process tried to
go up past the root or jail).

If we didn't bail out with an error, we now look at "abc".
This is not special syntax; it may require us to actually retrieve a
directory entry (which may or may not be cached in RAM, or
may or may not exist on disk).

Whatever dir we end up with, we take the dir->inode, fetch the inode
info and fill in the caller's struct stat from that.

Martin Vaeth

unread,
Sep 24, 2016, 9:31:13 AM9/24/16
to
Kaz Kylheku <221-50...@kylheku.com> wrote:
>
> The "current working directory" is an *object* in the kernel associated
> with the calling process.

Windows perhaps had (has?) such an inherently broken concept.
Unix is much more clever. Learn about inodes and how unix organizes
directories before making such nonsense claims.
Start e.g. by looking at an implementation of "cwd" in unix.
And to understand why the unix way is very clever, think about
what happens if you move a directory with millions of (recursive)
subdirectories and thousands of processes running, or if you
remove the current working directories of other processes.

Eric

unread,
Sep 24, 2016, 12:10:05 PM9/24/16
to
Thankyou!

It is clever, it is simple, it is beautiful, and ...
... it ain't broken!

Somebody higher up the thread said

The “.” and “..” links in directories serve no purpose except
to get in the way, and should be removed.

which is a reflection of both a limited outlook and a failure (or lack
of effort) to understand how and why something actually works before
judging it.

Anyone who needs to read directories and discard "." and ".." often
enough to be bothered by it should just write themselves a wrapper and
get on with it.

Why so many allegedly clever people want to talk about changing the
fundamentals of the system to deal with this sort of issue is totally
incomprehensible.

Kaz Kylheku

unread,
Sep 25, 2016, 10:40:27 AM9/25/16
to
On 2016-09-24, Martin Vaeth <mar...@mvath.de> wrote:
> Kaz Kylheku <221-50...@kylheku.com> wrote:
>>
>> The "current working directory" is an *object* in the kernel associated
>> with the calling process.
>
> Windows perhaps had (has?) such an inherently broken concept.

The infelicities in the Windows behavior w.r.t. open files being
in use has nothing whatsoever to do with "..".

> Unix is much more clever. Learn about inodes and how unix organizes
> directories before making such nonsense claims.

Assuming that people are idiots who haven't heard of an
inode is not a great way to enter into a comp.unix.*
debate, unless you have unassailable proof.

> Start e.g. by looking at an implementation of "cwd" in unix.

That is a user space command; perhaps you mean the getcwd
system call?

The argument is that ".." is removable. Looking at an implementation
which might depend on ".." doesn't prove that it isn't removable.

You cannot disprove a claim that "something can be otherwise"
solely by looking at how it is designed now.

> And to understand why the unix way is very clever, think about
> what happens if you move a directory with millions of (recursive)

What happens, when you move (within the same filesystem volume, using
the rename() system call) a directory with millions of recursive
subdirectories is the same thing as what happens when you move an empty
directory.

Have you not noticed in your hundreds of years of awe-inspiring Unix
experience how moving a directory is virtually instantaneous, whether
it contains next to nothing, or a subtree with millions of nodes?

> subdirectories and thousands of processes running, or if you
> remove the current working directories of other processes.

You and your little cheerleader "Eric" there didn't consider
that the above scenarios work fine in Unix systems inside
a partition which uses a Windows filesystem, or other flesystem
that doesn't have ".." links.

I'm confident as a software designer and kernel developer that all the
Unix semantics for open descriptors, cwd's and roots can be achieved
without "..", and certainly without ".".

The existence proof comes from the fact that we actually can mount
such filesystems.

I have prior experience developing a file system driver for a derivative
of BSD Unix, for a custom networked file system. Some fourteeen years
ago I worked on a job in which I implemented something very similar to
Linux's FUSE, but for the Mac OS/X Darwin kernel, redirecting the VFS
calls up to a daemon in user space. Most of my other kernel dev
experience is in non-fs related areas.

Eric

unread,
Sep 25, 2016, 1:10:04 PM9/25/16
to
On 2016-09-25, Kaz Kylheku <221-50...@kylheku.com> wrote:
8>< --------
> You and your little cheerleader

Cheerleader? I was arguing with you before he came along!

> "Eric"

It's my real name, quotes not needed.

> there didn't consider that the above scenarios work fine in Unix systems
> inside a partition which uses a Windows filesystem, or other flesystem
> that doesn't have ".." links.

I assume that their drivers are, in part, wrappers that make them look
as if they do.

> I'm confident as a software designer and kernel developer that all the
> Unix semantics for open descriptors, cwd's and roots can be achieved
> without "..", and certainly without ".".

Of course they can. In how many places will changes be required? How can
you guarantee that you have found them all? How many things that are now
independent will become interdependent? How can you ensure that something
that is currently obvious will not become unobvious. Would it really
be worth the effort just so that a few people who would rather complain
than write themselves a simple wrapper would be just slightly happier?

> The existence proof comes from the fact that we actually can mount
> such filesystems.
>
> I have prior experience developing a file system driver for a derivative
> of BSD Unix, for a custom networked file system. Some fourteeen years
> ago I worked on a job in which I implemented something very similar to
> Linux's FUSE, but for the Mac OS/X Darwin kernel, redirecting the VFS
> calls up to a daemon in user space. Most of my other kernel dev
> experience is in non-fs related areas.

Thanks for the CV but I don't have any job openings at the moment.

Martin Vaeth

unread,
Sep 25, 2016, 5:00:11 PM9/25/16
to
Kaz Kylheku <221-50...@kylheku.com> wrote:
>
> Assuming that people are idiots who haven't heard of an
> inode

If you really should know that (and _understand_ what it means):
How does it come that you claim such a nonsense that the kernel
stores an "object" contaning the full directory _path_
(in whichever form) instead of essentially just 1 inode
(I am slightly simplifyng here and ignore "mount" technicalities).
Indeed, for the rare cases that the full path _needs_ to be
obtained, it is _constructed at that time_ by using the
"current" and "parent" inodes it gets from the file system
(driver) which you falsely claimed to be superfluous.

> That is a user space command; perhaps you mean the getcwd
> system call?

Obviously, you didn't follow my suggestion and learnt how
it works under unix: Otherwise you would have seen that
they both do it the same way - which does not mean storing
"CWD" objects but constructing the full path when needed
(which is impossible without the "current" and "parent" inode
information from the filesystem).

> Have you not noticed in your hundreds of years of awe-inspiring Unix
> experience how moving a directory is virtually instantaneous, whether
> it contains next to nothing, or a subtree with millions of nodes?

When you realized this, then think about what the kernel would
have to do, if it would have thousands of "CWD" objects lying
around which include full directory paths.

> You and your little cheerleader

This and others of your comments show me that you are here mainly
for trolling. Therefore, this is EOD for me.

Kaz Kylheku

unread,
Sep 25, 2016, 9:42:01 PM9/25/16
to
On 2016-09-25, Martin Vaeth <mar...@mvath.de> wrote:
> Kaz Kylheku <221-50...@kylheku.com> wrote:
>>
>> Assuming that people are idiots who haven't heard of an
>> inode
>
> If you really should know that (and _understand_ what it means):
> How does it come that you claim such a nonsense that the kernel
> stores an "object" contaning the full directory _path_
> (in whichever form) instead of essentially just 1 inode
> (I am slightly simplifyng here and ignore "mount" technicalities).
> Indeed, for the rare cases that the full path _needs_ to be
> obtained, it is _constructed at that time_ by using the
> "current" and "parent" inodes it gets from the file system
> (driver) which you falsely claimed to be superfluous.

No, no, no. Nobody said that these parent links obtained from
the filesystem driver are superfluous *under the original Unix design*.
Obviously, they are not; they can't just be removed without making compensating
changes elsewhere!

It somehow went over head that, at a very high level, I was describing an
possible alternative design which addresses the requirement for parent
links if those are not available from the filesystem data structure.

> When you realized this, then think about what the kernel would
> have to do, if it would have thousands of "CWD" objects lying
> around which include full directory paths.

Now you just appear to cribbing from material I have already written myself
days ago. In Message-ID <201609230...@kylheku.com> on Friday, Sep. 23,
2016, I wrote the following:

KK> What the persistent ".." allows us to do is to throw away these
KK> in-kernel parent objects to save memory; we can pick them up from
KK> disk. This is probably why it was done that way; it's an old design
KK> decision that harkens back to the days when /vmunix was less than fifty
KK> kilobytes and memories were tiny.

I.e. I'm well aware of the memory tradeoff.

These thousands of cwd objects do not include full directory paths.
Moreover, they are shared wherever possible. If a thousand processes
share the same cwd, they share one object. If two or more cwd object have
the same parent, they share a parent object, and so on.

I see that in one of my postings in this thread I made an unfortunate bizarre
typo, and you might have latched onto that. On Thursday, Sep. 15, 2016, in
Message-ID: <201609150...@kylheku.com>, I wrote the following:

KK> [...] A process could have a CWD, and that CWD object could have
KK> is PWD (in memory only) and so on up to the root. [...]

This nonsensical "PWD" was just supposed to say "parent object"; I hadn't
intended to make references to PWD, which we understand to be a shell variable
holding a full path (quite irrelevant here).

Rakesh Sharma

unread,
Sep 29, 2016, 7:33:52 AM9/29/16
to
On Friday, 26 August 2016 04:37:30 UTC+5:30, James wrote:

>
> How to list directories (in a tree structure) which have no sub-directories?
>
> TIA
> James


This is a all-builtins based solution.

fx() {
cd "$1" && \
set X \
"$1" "${2-}" \
[*]/ */ \
.[[]!.[]][*]/ .[!.]*/ \
..[?][*]/ ..?*/ \
; shift

case "$*" in
"$1 $2 "'[*]/ */ .[[]!.[]][*]/ .[!.]*/ ..[?][*]/ ..?*/' )
case $2 in
'' ) echo "$1" ;;
*/ ) echo "$2$1" ;;
* ) echo "$2/$1";;
esac
return
;;
esac

(
set X "$1" "$2" [*]/ */; shift
case "$*" in
"$1 $2 "'[*]/ */' ) :;;
* )
base=$2${2:+/}$1
shift 3
while case $# in 0 ) break;; esac; do
(fx "$1" "$base")
shift
done;;
esac
)

(
set X "$1" "$2" .[[]!.[]][*]/ .[!.]*/; shift
case "$*" in
"$1 $2 "'.[[]!.[]][*]/ .[!.]*/' ) :;;
* )
base=$2${2:+/}$1
shift 3
while case $# in 0 ) break;; esac; do
(fx "$1" "$base")
shift
done;;
esac
)

(
set X "$1" "$2" ..[?][*]/ ..?*/; shift
case "$*" in
"$1 $2 "'..[?][*]/ ..?*/' ) :;;
* )
base=$2${2:+/}$1
shift 3
while case $# in 0 ) break;; esac; do
(fx "$1" "$base")
shift
done;;
esac
)
}

# invoke
fx .

--Rakesh

lawren...@gmail.com

unread,
Oct 2, 2016, 7:23:21 PM10/2/16
to
On Monday, September 26, 2016 at 3:40:27 AM UTC+13, Kaz Kylheku wrote:
> I'm confident as a software designer and kernel developer that all the
> Unix semantics for open descriptors, cwd's and roots can be achieved
> without "..", and certainly without ".".

I would have thought this went without saying.

All I can conclude, from the comments persistently disagreeing with you, and finding ever-more-creative ways to misunderstand your point, is that some people are so wedded to existing ways of doing things that they see any change as a threat to their peace of mind.

> The existence proof comes from the fact that we actually can mount
> such filesystems.

<thumbsup/>
0 new messages