On Mon, Jun 03, 2024 at 03:52:54PM +0200, Franco Martelli wrote:
> On 31/05/24 at 22:03, Greg Wooledge wrote:
> > > It could be improved adding the "-a" switch to show also the hidden
> > > directories and the "--color" switch to the "grep" command but this sadly
> > > doesn't show the expected result (colorized directories) I don't know why:
> > > 
> > > ~$ tree --du -Fah /tmp/x | grep --color /$
> > You're only coloring the trailing / characters.  If you want everything
> > from after the last space to the end of the line, you'd want:
> > 
> >      tree --du -Fh /usr/local | grep --color '[^[:space:]]*/$'
> 
> I realized why "tree" command doesn't colorized the directories: "tree"
> detects that its std output goes through a pipe and therefore it disables
> the escaped code to colorize (like also "dmesg" does).
> To avoid this behavior you must use the "unbuffer" command:
> 
> unbuffer tree --du -Fah /usr/local | grep /$

According to tree(1) there's a -C option which should force tree's
coloring to be always on, even when stdout goes to a pipe.  But tree
never colors anything for me, even with -C and no pipe, so I don't know
what else is needed.

> ...> If you want to avoid that, you can use xargs -0:
> > 
> >     duhs() { printf '%s\0' "${1:-.}"/*/ | xargs -0 du -sh; }
> > 
> > As printf is a bash builtin, it can handle an unlimited number of
> > arguments.  So this form should work in all cases.
> 
> Thank you very much for revolutionizing my duhs() function, but sadly this
> version omits to show the hidden directories. Do you have a version that it
> shows also those directories? For me it's so hard to figure out what the
> "${1:-.}"/*/  block does.

"$1" is the first argument given to the function.  It can also be written
as "${1}".

"${1:-default}" is the first argument given to the function, *or* the
constant string 'default' if the first argument is empty or not given.
Therefore, "${1:-.}" is "the first argument, or default to . if no
argument is given".

Since that part is quoted, whatever value is used is taken as a string,
with no word splitting or globbing applied.  That means it will handle
directory names that contain spaces, asterisks, etc.

Given the glob "x"/*/ the shell will look for all the subdirectories
of "x".  The trailing / forces it to ignore anything that isn't a
directory.

You can see it in action:

hobbit:~$ printf '%s\n' /usr/local/*/
/usr/local/bin/
/usr/local/etc/
/usr/local/games/
/usr/local/include/
/usr/local/lib/
/usr/local/man/
/usr/local/sbin/
/usr/local/share/
/usr/local/src/
/usr/local/tcl8.6/

So, putting it all together, that glob says "all the subdirectories of
the first argument, or all the subdirectories of . if there was no
argument given".

Getting it to include directory names that begin with . is trickier.
The simplest way would be to turn on bash's dotglob option, and then
turn it off again at the end:

duhs() {
    shopt -s dotglob
    printf '%s\0' "${1:-.}"/*/ | xargs -0 du -sh
    shopt -u dotglob
}

But this assumes that the option was *not* already on when we entered
the function.  If it was on, we've just turned it off.  Another way to
do this, which doesn't permanently alter the option for the remainder
of the shell's lifetime, would be to wrap the function body in a subshell:

duhs() {
    (
      shopt -s dotglob
      printf '%s\0' "${1:-.}"/*/ | xargs -0 du -sh
    )
}

The formatting choices here are legion.

There are a few other options, but they become increasingly arcane from
here.  The subshell is probably the most straightforward choice.  The
function is already forking child processes, so from a performance point
of view, adding a subshell won't be much worse.

I'll also throw in one last piece of information because if I don't,
someone else is likely to do it, without a good explanation.
Syntactically, the body of a shell function doesn't have to be enclosed
in curly braces.  The body can be any compound command, and a curly-brace
command group is just one example of a compound command.  A subshell is
another example.  So, it could also be written this way:

duhs() (
    shopt -s dotglob
    printf '%s\0' "${1:-.}"/*/ | xargs -0 du -sh
)

I'm not personally fond of this.  It's extremely easy to overlook
the fact that the curly braces have been replaced with parentheses,
especially in certain fonts.  Nevertheless, some people like this.

Reply via email to