On Sun, Feb 09, 2025 at 06:35:38 +0700, Robert Elz wrote:
> ps: there is almost never a good excuse to use non-standard sh
> extensions (bash's, or any other shells), when writing a standard
> conforming script would allow any Bourne shell variant to
> work, and that's certainly the case here, and in most other cases.

You really want bash's ${var//search/replace} feature for the
renaming step, so as long as you're already using that, you might
as well use any other bash features that make your job easier.

For the problem as stated (no recursion), we can discard the "ls"
command, the temporary file that holds the list of files, and all
variations of the pipeline.  There's no stdin conflict, so we don't
need to open FD 3.  The only bash extensions we really want, then,
are the parameter expansion for the renaming step, and "read -p" for
the prompt.  We could use case instead of [[ for checking the user's
input, but why?  We're already using two bash extensions, and [[ is
nicer to use.

    for f in *[[:space:]]*; do
        read -r -p "Fix <$f>? " yn
        if [[ $yn = [Yy]* ]]; then
            mv -- "$f" "${f//[[:space:]]/_}"
        fi
    done

Everything else was a demonstration of how the code might look if we
were solving some *other* problem, slightly similar to the original,
for educational purposes.

If I were going to write this in POSIX sh, the read -p could be
replaced with printf + read, and the [[ with case, but the name
alteration becomes *ugly*.

    for f in *[[:space:]]*; do
        printf 'Fix <%s>? ' "$f"
        read -r yn
        case $yn in
            [Yy]*)
                new=$(
                    printf %s "$f" | tr '[:space:]' _
                    printf x
                )
                new=${new%x}
                mv -- "$f" "$new"
                ;;
        esac
    done

Forking multiple processes just to replace some characters in a string
is sad.  I'd much rather use bash for this.  Or just use the perl rename
script that's been available on most Linux distributions for ages:

    prename 's/\s/_/g' *[[:space:]]*

P.S. Yeah, I could have skipped the "printf x" and ${new%x} steps here,
because it's not possible for tr's output to end with a newline (even
if the original filename does), because the newline is replaced with
a _ character.  Or at least, that's what happens in any normal locale.
But I left them in because, again, someone might adapt this code to a
*slightly different* problem where the data stream could end with newline
character(s), which would be destroyed by the command substitution.

Reply via email to