1

There is a bash command file(1) which outputs a file's contents type according to the matching pattern definitions in the /etc/magic and /usr/share/misc/magic.

Is there a way to use these pattern definition "magic" files as a test for the "find" command?

For example, there are some text files and "PalmOS application" files

file -b palmapp
PalmOS application

file -b readme.txt
ASCII text

I need some way to find the files which match "PalmOS application" rather than "ASCII text", for example this is an ideal command:

find . -magic "PalmOS*"

Is there any idea to write a single-line bash command making for each file this test?

migrated from serverfault.com Jan 19 '15 at 15:19

This question came from our site for system and network administrators.

3

Something along the lines of:

find . -print -exec file {} \; | grep "PalmOS"

To tidy up the output pipe through awk as well by adding:

| awk '{print substr($1,2,length($1)-2)}'

1

There's no built-in feature of find to analyze the file content, but you can make it invoke file. Using the shell to combine multiple task-specific programs (find to traverse directory trees, file to analyze file content) is the Unix way of doing things.

You want to act on files for which the output of the command file -b -- "$filename" contains the string PalmOS application. That can be tested with the shell script

case "$(file -b -- "$filename")" in *"PalmOS application"*) true;; *) false;; esac

You can use this script in a find command like this:

find . -type f -exec sh -c 'case "$(file -b "$0")" in *"PalmOS application"*) true;; *) false;; esac' {} \; -print

Replace -print by the action you want to take (which can be another -exec …). This second action is only executed if the previous action is true, which is when the shell command returns 0 (i.e. true).

Alternatively, in zsh, you can let the shell do the directory traversal with the ** glob. The . glob qualifier restricts the matches to regular files (like -type f for find).

for x in **/*(.); do
  [[ $(file -b -- $x) = *"PalmOS application"* ]] || continue
  …
done

In bash ≥4.3, you can use ** for recursive globbing, but you need to enable the globstar option first. In bash 4.0–4.2, globstar is already available, but beware that ** traverses symbolic links to directories. In ksh93, use set -o globstar instead of shopt -s globstar.

shopt -s globstar
for x in **/*; do
  [[ -f $x ]] || continue
  [[ $(file -b -- "$x") = *"PalmOS application"* ]] || continue
  …
done

If your file names don't contain whitespace or any of :\"' then an alternate method is to run file on the file names and parse the output.

find -type f |
xargs file |
sed -n 's/:.*PalmOS application.*//p' |
xargs somecommand

If your file names contain whitespace but no newlines or colons then you can use the shell to do the parsing.

find -type f |
while IFS=: read -r filename type; do
  case "$type" in
    *"PalmOS application"*)
      …;;
  esac
done
0

This doesn't use find, but setting pattern and directory will produce the results your question asks for:

pattern="PalmOS application"
directory=.

for f in $directory/*; do
    if [[ $(file -b $f) == "$pattern" ]]; then
        echo $f; 
    fi; 
done

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy