5

I'd like to get the MIME type of all files under the current directory. Why doesn't this work? I've tested bash on OSX or Linux. "file" complains that it can't find the file, but if I run it with the exact same path it works.

$ find . -type f | xargs -n 1 -I FILE echo $(file --mime-type -b FILE)
ERROR: cannot open './foo.txt' (No such file or directory)
...
$ file --mime-type -b ./foo.txt
text/plain

Of course in the real world I don't just "echo" the response, I need to use the MIME type string in another command.

This is the simplest I could make the problem. I guess I'm not understanding something about xargs filename substitution?

5

The command substitution $(file --mime-type -b FILE) is executed by the shell before being passed to xargs, which is why you are not seeing what you need. Quote $(file --mime-type -b FILE) and pass it to bash -c via xargs

find . -type f | xargs -n 1 -I FILE bash -c  'echo $(file --mime-type -b FILE)'
  • 1
    ... so making a file named 'FILE' will demonstrate that nicely. – jthill Nov 13 '13 at 14:58
4

There is no need to make use of xargs here. Simply make use of find's -exec switch.

$ find . -type f -exec file --mime-type -- {} +

Examples

default file output

$ find 8* -type f -exec file --mime-type -- {} + | tail -5
89999/sample10.txt:               text/plain
89999/sample2.txt:                text/plain
89999/sample4.txt:                text/plain
89999/sample6.txt:                text/plain
89999/sample9.txt:                text/plain

file -b output

$ find 8* -type f -exec file --mime-type -b -- {} + | head -5
application/x-empty
text/x-perl
text/plain
text/plain
text/plain

Alternatives

This is just an FYI, but there is another command called mimetype which you can also use to do the same thing as file --mimetype. This command is part of this package on Fedora, perl-File-MimeInfo, and works similarly:

$ find 8* -type f -exec mimetype -- {} + | tail -5
89999/sample10.txt:               text/plain
89999/sample2.txt:                text/plain
89999/sample4.txt:                text/plain
89999/sample6.txt:                text/plain
89999/sample9.txt:                text/plain

More elaborate exec

Given you inquired about "doing more than just echo.." and since you're attempting to use xargs I'm going to conclude that you're thinking you'll need to do more elaborateness with xargs.

But if you're using find, this is generally not the best way to go. You can instead use find, and then within find's -exec switch call a shell and do more complicated things within here, rather than try to coax xargs into doing this in a more complex way.

$ find . -type f -exec sh -c '
   cmd1;
   cmd2;
   file --mime-type -b "$@";
   cmd3;
   cmd4;
 ' sh {} \;

NOTE: That I've switched from using the + terminator which passes multiple arguments to file --mime-type ... to \; which passes one at a time.

Example

$ find 8* -type f -exec sh -c '
   echo -n "mime-type: "; 
   file --mime-type -b "$@"
  ' sh {} \; |& tail -10
mime-type: text/plain
mime-type: text/plain
mime-type: text/plain
mime-type: text/plain
mime-type: text/x-shellscript
mime-type: text/plain
mime-type: text/plain
mime-type: text/plain
mime-type: text/plain
mime-type: text/plain
  • If you don't wrap it, it will undergo shell globbing. Whether that's what you want or not is subjective -- I think what you actually want is find . -name '8*', but it's hard to tell. – Chris Down Nov 13 '13 at 5:07
  • @ChrisDown - yeah in my sample data directory I actually did want 8* 8-). But that's just b/c of my setup. – slm Nov 13 '13 at 5:09
3

Your problem is about shell expansion. $() is expanded before it is handed over as an argument. If you want to treat it as-is, single quote it and call a shell for it.

Also, if you process filenames, get in the habit of using print0 and 0 as leaving them out will produce problems for you with filenames containing space character.

Example:

find . -type f -print0 | xargs -0 -n 1 -I FILE bash -c 'echo $(file --mime-type -b FILE)'

In your case this can be simplified to:

find . -type f -print0 | xargs -0 -n 1 file --mime-type -b

An additional comment: Calling a program for all files found (be it 'xargs -n 1' or find+exec) can be a real performance bottleneck. If you have a directory with thousands of files, you will spawn thousands of processes.

  • There is a -P option to set max processes in xargs. – manish_s Nov 1 '14 at 18:16
  • It will still generate a lot of processes, just limits the concurrent number of them. The point is to avoid, whenever possible, a "generate a lot of items -> call a process for each", instead do "generate a lot of items -> call a process that can handle a lot of items". Very basic example to show you the point: Don't call grep for each pattern individually, as a single grep can read a pattern file. – Bgs May 9 '18 at 17:18

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.