Skip to main content
added 793 characters in body
Source Link
Stéphane Chazelas
  • 553.6k
  • 92
  • 1.1k
  • 1.6k

For a variant that does both and and not:

findn() (
  if [ -t 1 ]; then # if the output goes to a terminal
    action=-print  # simple print for the user to see
  else
    action=-print0 # NUL-delimited records so the output can be post-processed
  fi
  first=true
  for arg do
    if "$first"; then
      set -- "$@" '('
      first=false
    else
      set -- "$@"
    fi
    if [ "$arg" = ! ]; then
      set -- "$@" !
    else
      case $arg in
        (*[][*?\\]*)
          # already contains wildcard characters, don't wrap in *
          set -- "$@" -iname "$arg"
          ;;
        (*)
          set -- "$@" -iname "*$arg*"
          ;;
      esac
    fi
    shift
  done
  "$first" || set -- "$@" ')'
  exec find . "$@" "$action"
)

For the filenames that contain both foo and bar and not baz.

In that variant, I also made it so that if the argument contained a wildcard character, it was taken as-is, so you can do:

findn foo ! 'bar*'

To look for files that do not start with bar. If you're using the zsh shell, you can make an alias:

alias findn='noglob findn'

To disable globbing on that command which allows you to write:

find foo ! bar*

You may want to make that a script (here a sh script is enough as that syntax is POSIX) instead of a function, so it can be called from anywhere instead of just your shell.

findn() (
  if [ -t 1 ]; then # if the output goes to a terminal
    action=-print  # simple print for the user to see
  else
    action=-print0 # NUL-delimited records so the output can be post-processed
  fi
  first=true
  for arg do
    if "$first"; then
      set -- "$@" '('
      first=false
    else
      set -- "$@"
    fi
    if [ "$arg" = ! ]; then
      set -- "$@" !
    else
       set -- "$@" -iname "*$arg*"
    fi
    shift
  done
  "$first" || set -- "$@" ')'
  exec find . "$@" "$action"
)

For the filenames that contain both foo and bar and not baz.

For a variant that does both and and not:

findn() (
  if [ -t 1 ]; then # if the output goes to a terminal
    action=-print  # simple print for the user to see
  else
    action=-print0 # NUL-delimited records so the output can be post-processed
  fi
  first=true
  for arg do
    if "$first"; then
      set -- "$@" '('
      first=false
    else
      set -- "$@"
    fi
    if [ "$arg" = ! ]; then
      set -- "$@" !
    else
      case $arg in
        (*[][*?\\]*)
          # already contains wildcard characters, don't wrap in *
          set -- "$@" -iname "$arg"
          ;;
        (*)
          set -- "$@" -iname "*$arg*"
          ;;
      esac
    fi
    shift
  done
  "$first" || set -- "$@" ')'
  exec find . "$@" "$action"
)

For the filenames that contain both foo and bar and not baz.

In that variant, I also made it so that if the argument contained a wildcard character, it was taken as-is, so you can do:

findn foo ! 'bar*'

To look for files that do not start with bar. If you're using the zsh shell, you can make an alias:

alias findn='noglob findn'

To disable globbing on that command which allows you to write:

find foo ! bar*

You may want to make that a script (here a sh script is enough as that syntax is POSIX) instead of a function, so it can be called from anywhere instead of just your shell.

Source Link
Stéphane Chazelas
  • 553.6k
  • 92
  • 1.1k
  • 1.6k

One way with GNU find or compatible (-iname is already a GNU extension anyway) could be to define the function as:

findn() (
  if [ -t 1 ]; then # if the output goes to a terminal
    action=-print  # simple print for the user to see
  else
    action=-print0 # NUL-delimited records so the output can be post-processed
  fi
  first=true
  for arg do
    if "$first"; then
      set -- "$@" '('
      first=false
    else
      set -- "$@" -o
    fi
    set -- "$@" -iname "*$arg*"
    shift
  done
  "$first" || set -- "$@" ')'
  exec find . "$@" "$action"
)

Then you can use it as:

findn foo bar

To see the file names that contain foo or bar (change the -o to -a above if you want instead the ones that contain both foo and bar).

And:

findn foo bar | xargs -r0 cat

If you want to apply a command on each file found by findn.

findn() (
  if [ -t 1 ]; then # if the output goes to a terminal
    action=-print  # simple print for the user to see
  else
    action=-print0 # NUL-delimited records so the output can be post-processed
  fi
  first=true
  for arg do
    if "$first"; then
      set -- "$@" '('
      first=false
    else
      set -- "$@"
    fi
    if [ "$arg" = ! ]; then
      set -- "$@" !
    else
       set -- "$@" -iname "*$arg*"
    fi
    shift
  done
  "$first" || set -- "$@" ')'
  exec find . "$@" "$action"
)

And then:

findn foo bar ! baz

For the filenames that contain both foo and bar and not baz.