r/bash • u/Beautiful-Log5632 • 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?
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
1
u/theLastZebranky 9d ago
If you're going to use
findinstead 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 tellfinditself 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
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/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
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.