2

I'd like to open a randomly selected file via the terminal. I found shuf, which seems to be just the utility that I need, although I cannot figure out a way to make the command I'm working with recursive.

Initially I used open $(ls | gshuf -n1), which works great if I have a flat directory structure. How can I make this recurse into any number of subdirs and filter out files like .DS_Store and folders?

jml
  • 255

1 Answers1

3

The right tool is find. It works recursively, this solves your main problem. You can exclude various patterns if you know how.

Basic usage will be like:

open "$(find . -type f | shuf -n1)"

Newlines in filenames will break this. Your tools may or may not support non-POSIX options that allow to pass NUL-terminated data. This snippet works in my Debian:

find . -type f -print0 | shuf -z -n1

although if you embed it in $(…), trailing newlines (if any) will still be removed.

To exclude names you can use syntax like ! -name .DS_Store, but to exclude entire subdirectories you need -prune. There are pitfalls:

  • The order of operands matters, e.g. -prune for a directory should be before -type f, -print/-print0 usually belongs at the end.
  • Logical "or" (-o) often requires parentheses and it's not as intuitive as you may wish.
  • Omitting -print/-print0 may give you more results than you expect. With complex logic it's good to explicitly include -print/-print0.

Study man 1 find to learn more. This is a working example that excludes two directories and two name patterns:

find . \( -name dir1 -o -name "dir 2" \) -prune -o -type f ! \( -name "*.txt" -o -name "echo*" \) -print

Since you need $(…) and I told you to quote properly, you should know that quotes inside $(…) are parsed separately. E.g. this is properly quoted:

open "$(find . -type f ! -name "not this file" | shuf -n1)"

(compare this answer, quirk 2).