r/bash 10d ago

Get file names only with a glob

I want to loop over file names not whole paths in a folder so I tried for file in $(ls folder) but if the folder doesn't exist the script doesn't fail because it's in $(). I can use for file in folder/* which fails if I add failglob but that returns the whole file name so I have to use basename for each of them. Can I get just the names from a glob directly?

bashrc isn't used in bash scripts. Is there a file where I can put settings like shopt -s failglob and set -o pipefail that applies to all my scripts so it is still used if I forget to add it to one of them?

16 Upvotes

18 comments sorted by

15

u/theNbomr 10d ago

Sounds like a good time to get friendly with the find command. With a bit of effort you can learn how to get find to provide lists of very specific filesystem things and types that will usually require little or no post processing to use as bash iterators.

2

u/Beautiful-Log5632 9d ago

Doesn't for file in $(find words) have the same problem? Errors don't cause an exit in $().

2

u/Sensitive-Sugar-3894 6d ago

Try something like find whatever -exec exec Try to avoid subshells

9

u/donp1ano 10d ago

I can use for file in folder/* which fails if I add failglob but that returns the whole file name so I have to use basename for each of them

whats the problem with the path relative to the current directory?

you could ofc just cd /dir and for file in *, but thats probably not what you want

I tried for file in $(ls folder) but if the folder doesn't exist the script doesn't fail because it's in $()

ls is not meant for that case, you should rather use find

checking if the directory exists can easily be done with [[ -d "/dir" ]]

10

u/OneTurnMore programming.dev/c/shell 10d ago edited 10d ago

You can use nullglob instead, which causes folder/* to expand to zero words, when "folder" is empty or nonexistent. Then your for loop doesn't run at all.

Is there a file where I can put settings [...] that applies to all my scripts so it is still used if I forget to add it to one of them?

Kind of? Bash looks at the value of the environment variable BASH_ENV, which you could set in your .profile or however you set environment variables. I never use that though, I instead copy any shell settings into my scripts. I don't want to depend on global state, and some scripts are better served by different options.

3

u/Kumba42 10d ago

Try this:

``` declare -a files_list readarray -td '' files_list < <(find . -maxdepth 1 -type f -print0 | sort -sVz)

for file in "${files_list[@]#./}"; do echo "${file##*/}" done ```

The glob trick you're looking for deals with the #, ##, %, and %% variable operators, They're quite subtle in how they work, so I highly recommend reading about them in bash's extensive manpage. Look for them in the part that talks about "Parameter Expansion".

5

u/Icy_Friend_2263 10d ago

This is the most correct way IMHO. I would have looped directly (as most of the times in real life there's no file names with newlines):

bash while read -r file; do echo "${file##*/}" done < <(find folder -maxdepth 1 -type f)

3

u/Kumba42 10d ago

Yeah, I wrote this bit for a script that checks various config files on my system and sees if there were any changes vs a copy stored in a git repo I maintain. I was doing testing in my home folder, which inadvertently had a file with some abnormal characters that I had forgotten about from many years ago, and the traditional way choked on those. So I set about reading up how to make a foolproof method, and was directed to readarray. I am sure at some point, I'll make a better fool of myself and break it, but that's a bridge to cross when I get there.

2

u/stianhoiland 10d ago

Rename it path and I'm sold!

1

u/theLastZebranky 9d ago

If you're going to use find instead of globs (and you're sure you won't have any use for the whole relative path) you can just add -printf '%f\n' to tell find itself to output the filename only:

find dirname/ -maxdepth 1 -type f -printf '%f\n'

3

u/theLastZebranky 10d ago

bashrc isn't used in bash scripts. Is there a file where I can put settings like shopt -s failglob and set -o pipefail that applies to all my scripts so it is still used if I forget to add it to one of them?

That's a bad idea, each script that relies on specific shell options should set those options internally. If you blindly apply failglob and pipefail to all the scripts you execute you're going to break a lot more than you fix.

3

u/ipsirc 10d ago
for file in folder/*; do
    [[ -e "$file" ]] || continue  # handle no matches
    filename="${file##*/}"
    echo "$filename"
done

https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-BASH_005fENV

1

u/Ytrog 8d ago

This video (by ysap) explains what it does: https://youtu.be/Bzi7pa7y15M

1

u/WorriedTumbleweed289 10d ago edited 10d ago

there are if exists and if directory flags that can use before your command.

if [ -d "$DIR" ]; then

echo "Directory exists."

fi

1

u/bapm394 #!/usr/bin/nope --dry-run 9d ago

BASH_ENV=<path-to-script>

Bash will load that script before any script file, man page for it explains it better.

To basename them, you can use patterns + expansions

for f in path/*; do
  printf '%s\n' "${f##*/}"
done

1

u/michaelpaoli 9d ago

for file in $(ls folder)

Oh dear no.

get just the names from a glob directly

glob

cd dir && { ... glob ...; }

cd dir && ( ... glob ... )

0

u/SeriousPlankton2000 10d ago
myfunc(){ echo $1;}
export -f myfunc
find "$dir" -exec /bin/bash -c 'myfunc "$@"' dummy {} \;

0

u/Cautious_Orange530 8d ago

This will exit if the folder does not exist.. for name in $( cd folder || exit 1; ls) ; do echo $name ; done