Search code examples
bashshellglob

bash - forcing globstar asterisk expansion when passed to loop


I am attempting to write a script that tried to use globstar expressions to execute a command (for example ls)

#!/usr/bin/env bash

shopt -s globstar nullglob
DISCOVERED_EXTENSIONS=$(find . -type f -name '*.*' | sed 's|.*\.||' | sort -u | tr '\n' ' ' | sed "s| | ./\**/*.|g" | rev | cut -c9- | rev | echo "./**/*.$(</dev/stdin)")

IFS=$'\n'; set -f
for f in $(echo $DISCOVERED_EXTENSIONS | tr ' ' '\n'); do
  ls $f;
done

unset IFS; set +f
shopt -u globstar nullglob

The script output is:

ls: ./**/*.jpg: No such file or directory
ls: ./**/*.mp4: No such file or directory

It is passing ls "./**/*.avi" instead of ls ./**/*.avi (no variable expansion). I attempted to use eval, envsubst and even used a custom expand function, to no avail

The result of echo "$DISCOVERED_EXTENSIONS" is:

./**/*.jpg ./**/*.mp4

What changes can be recommended so that value of $f is the result of glob expansion and not the expression itself?

EDIT: I'm keeping the question up as I have resolved my problem by not using globstar at all which solves my immediate problem but doesn't solve the question.

As pynexj points out, the set -f un-does shopt -s globstar nullglob so that makes the script I've written as non-functional 'cause removing set -f breaks this script


Solution

  • $f is the result of glob expansion

    The result of glob expansion is a list of arguments. It could be saved in an array. Saving it is just calling a subshell and transfering data.

    mapfile -t -d '' arr < <(bash -c 'printf "%s\0" '"$f")
    ls "${arr[@]}"
    

    Notes:

    • Do not do for i in $(....). Use a while IFS= read -r loop. Bashfaq how to read a stream line by line.
    • I have no idea what is going on at that DISCOVERED_EXTENSIONS long line, but I would find . -maxdepth 1 -type f -name '*.*' -exec bash -c 'printf "%s\n" "${0##*.}"' {} \; | sort -u.
    • I usually recommend using find instead of glubulation and working on pipelines/streams. I guess I would write it as: find . -maxdepth 1 -type f -name '*.*' -exec bash -c 'printf "%s\n" "${0##*.}"' {} \; | sort -u | while IFS= read -r ext; do find . -type f -name "*.$ext" | xargs -d '\n' ls; done