So I have to search a directory for files that are of 3 character length. That is easy, the problem is that at least 1 of the 3 characters needs to be either a "B" or a "y". I know that [By] would make sure the character is either a B or a y, however simply doing [By][By][By] would not suffice since it is not a requirement for all 3 characters to be either B or y; only 1 of them has to be. Please help.
3 Answers
You can do a pattern match in a for-loop.
for file in ./???; do
[[ -e $file && $file = *[By]* ]] || continue
printf 'Do stuff with <%s>\n' "$file"
done
Or you can enable extended globs by running shopt -s extglob
. Then you can use something like @([By]??|?[By]?|??[By])
shopt -s extglob
for file in ./@([By]??|?[By]?|??[By]); do
[[ -e $file ]] || continue
printf 'Do stuff with <%s>\n' "$file"
done
Either will be more efficient than involving grep
.
If you're scripting for POSIX sh, you could do something like this:
for file in ./???; do
[ -e "$file" ] || continue
case $file in
*[By]*) : ;;
*) continue;;
esac
printf 'Do something with <%s>\n' "$file"
done
Lastly, you could use three globs, though that will iterate the files in a different order and you risk iterating the same file multiple times.
for file in ./[By]?? ./?[By]? ./??[By]; do
[ -e "$file" ] || continue
printf 'Do something with <%s>\n' "$file"
done
-
If a file was found using
for file in ...
, why should you have doubts about its existence? :D Sep 29, 2013 at 10:43 -
1@RaduRădeanu if there are no files matching the glob, the glob will be passed on as the word to iterate, unless nullglob is enabled.– geirhaSep 29, 2013 at 11:05
-
1+1 for a pure bash solution, but if you're going to use extglob then you may as well use nullglob too. With nullglob, you could even avoid using a loop, if all you want is a list of the names --
printf '%s\n' ./@([By]??|?[By]?|??[By])
– evilsoupSep 29, 2013 at 12:56
printf '%s\n' ??? | grep '[By]'
The printf '%s\n'
prints each argument passed to it, one-per-line; the ???
is a glob that expands to every three-character filename in the working directory. This is piped to the grep
, which prints only those filenames containing the pattern you specified.
By the way, I found myself using printf '%s\n'
so much that I put the following in my ~/.bashrc
for ease-of-typing:
alias necho='printf "%s\n"' ## newline-echo
Most of the time this will be fine: it will break on filenames containing newlines, but those are pretty rare in fact (I've only ever seen them when I've made them specifically to test scripts of mine). It's probably possible to whip up a simple pure bash solution using extglob, but you could also use find
to work around this issue:
find . -name '???' -a -name '*[By]*'
This will act recursively by default; if you don't want that, use -maxdepth 1
.
find . -maxdepth 1 -name '???' -a -name '*[By]*'
-
-
@geirha I know that it's best-practice to make things safe for newlines... but honestly, I've only ever seen such files when I've created them to test scripts of mine. However, I'll whip up a
find
-based solution to make things absolutely safe. If you're referring to quoting the[By]
-- good point, that was a silly mistake.– evilsoupSep 29, 2013 at 12:23 -
+1
for thefind
solution! you'll be able to-exec
stuff safely with that one, or to further filter (as said with, e.g.,-maxdepth
ortype f
, etc.). And it might be quicker than Bash globbing if there's a huge number of files. Sep 30, 2013 at 17:17
Try this command(s):
cd /path/to/directory
ls | while read file; do if [ ${#file} -eq 3 ]; then echo $file; fi; done | grep '[By]'
or:
for file in ???; do echo $file; done | grep '[By]'
or, a variant without any loop:
ls | grep -ow '\w\{3\}' | grep '[By]'
ls
will list all files from the current directory, then we select only the filenames that have exactly 3 characters in length and finally, using grep
, we select only filenames that contain B
or y
.
-
-
1The
ls
isn't doing anything in the otherwise-superior second command...– evilsoupSep 27, 2013 at 10:55 -
1There's probably not filenames with whitespace in this case, but treating filenames as lines is a bad practice in general. Though the main problem here is that the pattern provided to grep should be quoted, if the current directory contains a file named
B
and one namedy
, the shell will replace[By]
withB y
, so you end up runninggrep B y
which will look for lines matchingB
in the file namedy
; ignoring stdin completely.– geirhaSep 29, 2013 at 10:19 -
1@RaduRădeanu, ah you're not using a posix shell. Try it with bash, the default login shell in ubuntu.– geirhaSep 29, 2013 at 12:23
-
1-1 because 1) Never parse
ls
, 2) what @geira said, 3) What is the point of pipingls
tofor
anyway? It does exactly the same thing as thefor
loop alone. 4) Yougrep -ow
does not do what you think it does, it will simply print out the first 3 characters of every file that contains three successive[A-za-z_]
characters followed by a non word one, so it will also match onfoo123
or 123foo` etc, irrespective of its length.– terdonSep 30, 2013 at 15:38