Syntax error when using find and sh -c - macos

When I run this command in Terminal:
find . -type f -name "*.png" -exec sh -c "file {} | egrep -o '^.*\d+,'" \;
I get this error if a filename contains parentheses:
sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `file ./(terrible filename).png | egrep -o '^.*\d+,''
I know it has something to do with the sh -c, but I don't know how to fix it, thanks.
./(terrible filename).png: PNG image data, 512 x 512,
// trying to get this result

You are basically pasting the file name into sh -c '...' without any quoting. The string inside sh -c after the substitutions made by find needs to be valid sh syntax, which means there can be no unquoted single quotes, parentheses, etc.
A more robust approach is to use -exec file {} and pass all the output from find to egrep.
find . -type f -name "*.png" -exec file {} \; | egrep -o '^.*\d+,'
The placeholder token {} gets replaced by find with the filename currently being processed. When it is a lone token, find can pass in any file name at all; but if you interpolate it into a longer string, such as a shell command, you will need to ensure that any necessary quoting etc. is added somehow. That's messy, so usually you will want to find a solution where you don't need to do that.
(As pointed out in comments to the other answer, -exec sh -c 'file "$1"' _ {} \; is another way to accomplish that; this generalizes to arbitrarily complex shell commands. If your find supports exec {} \+ you want to add a simple loop: -exec sh 'for f; do file "$f"; done' _ {} \+ -- incidentally, the _ is a dummy placeholder for $0.)

Are there parentheses in the file names? This might help:
find . -type f -name "*.png" -exec sh -c "file '{}' | egrep -o '^.*\d+,'" \;

Related

I used the'-print0' option to handle filenames with spaces, but I get an error

#!/bin/bash
find /home/data -name '*QQ*' -print0 -exec bash -c ' mv $1 ${0/\-QQ/-TT}' {} \;
I used the'-print0' option to handle filenames with spaces, but I get an error
/home/data/gone to sea.1080p-QQ.mp4mv: target 'sea.1080p-TT.mp4' is not a directory
Which part is wrong?
Thanks
You don't need -print0, since you're not piping the output to another program.
You just need to quote properly in the bash command.
find /home/data -name '*-QQ*' -exec bash -c 'mv "$1" "${1/\-QQ/-TT}"' {} {} \;
This should work as long as the filenames don't contain double quote or $ characters.
You could also avoid bash -c by using the rename command:
find /home/data -name '*-QQ*' -exec rename 's/-QQ/-TT/' {} +

How do I rename files found with the find command

I have a series of music folders. Some of the file names contain an underscore which I would like to get rid of.
With
find /Users/Chris/CDs -type f -name "*_*"
I find all of the files with underscores.
it appears that I can add -execdir mv {} to the command but do not know what to add from there.
I think {} provides the full path and file name as a string of the file with underscores but I do not know how to use something like sed 's/_//g' to remove the _ on the new file name.
Any help would be greatly appreciated.
Try:
find /Users/Chris/CDs -type f -name "*_*" -execdir bash -c 'mv -i -- "$1" "${1//_/}"' Mover {} \;
How it works:
-execdir bash -c '...' Mover {} \;
This starts up bash and tells it to run the command in the single quotes with Mover assigned to $0 and the file name assigned to $1.
mv -i -- "$1" "${1//_/}"
This renames file $1. This uses bash's parameter expansion feature, ${1//_/}, to create the target name from $1 by removing all underlines.
The option -i tells mv to ask interactively before overwriting a file.
The option -- tells mv that there are no more options. This is needed so that files whose names begin with - will be processed correctly.
Example
Let's start with a directory with these files:
$ ls
1_2_3_4 a_b c_d
Next we run our command:
$ find . -type f -name "*_*" -execdir bash -c 'mv -i -- "$1" "${1//_}"' Mover {} \;
After the command completes, the files are:
$ ls
1234 ab cd
The purpose of $0
Observe this command where we have added an error:
$ find . -type f -name "*_*" -execdir bash -c 'foobar -i -- "$1" "${1//_}"' Mover {} \;
Mover: foobar: command not found
Note that Mover appears at the beginning of the error message. This signals that the error comes from within the bash -c command.
If we replace Mover with -, we would see:
$ find . -type f -name "*_*" -execdir bash -c 'foobar -i -- "$1" "${1//_}"' - {} \;
-: foobar: command not found
When running a single command in a terminal, the source of the error may still be obvious anyway. If this find command were buried inside a long script, however, the use of a more descriptive $0, like Mover or whatever, could be a big help.

Input redirection in find -exec option

Why doesn't this command work?
find / -type f -name "*.c" -exec wc -c < \{} \;
I'm trying to hide the filename while showing number of characters
You can do the following:
find / -type f -name "*.c" -exec wc -c {} + | awk '{print $1}'
< does not work because redirection applies to the find command itself. So what you can do is use awk to only print the first column
As pointed out by #Charles Duffy, this is a bit more efficient since it means we're only starting one wc process, and no shell wrapper, once per file (as the awk instance is shared, not invoked per-file).
Use a bash command with exec like below
find . -type f -name "*.c" -exec bash -c 'wc -c < "$1"' _ {} \;
In your case the redirection < applies to the find command itself resulting a syntax error as there is no file literally named {}.
A sidenote : The positional parameter inside bash shell should be double quoted to deal with non-standard filenames, say those with spaces and so.
Edit
To NOT pay the extra cost of invoking multiple sub-shells, we can further improve the exec part like below
find . -type f -name "*.c" -exec bash -c 'for arg; do wc -c <"$arg"; done' _ {} +
Courtesy #Charles Duffy for this suggestion.
If you really want the redirection, you could do it in a subshell:
find / -type f -name "*.c" -exec sh -c 'wc -c < "{}"' \;
Edit: don't do this - see comments below!

Interpret bash commands

I checked some resources, but still hard to find a clue to interpret the codes.
$ find . -iname "*.dwp" -exec bash -c 'mv "$0" "${0%\.dwp}.html"' {} \;
$ find . -name ".DS_Store" -exec rm {} \;
To be more specific, what's the difference between -iname and -name? And what does "-c" and "%" symbolize?
Can you interpret the two commands a bit for me?
The first one:
-iname "*.dwp", indicate to the find command to find files whose name matches the pattern *.dwp, ignore case, e.g.: ./a.dwp
-exec expression {} \; part, execute the command bash -c 'mv "$0" "${0%\.dwp}.html"' {}. {} will be replaced by the path of each file. The expression is terminated by a semicolon. If there is a file a.dwp in the current directory, bash -c 'mv "$0" "${0%\.dwp}.html"' a.dwp will execute.
bash -c 'mv "$0" "${0%\.dwp}.html"' {}:
-c means read command from string, do not start an interactive shell.
$0 is the argument of the command, a.dwp in this example.
${0%\.dwp}.html is string manipulation, % removes the shortest match from the end, so for a.dwp, remove .dwp from end to get the file name a without extension.
So the command is mv a.dwp a.html.
The second one is very simple if you understand first one.

What's the difference between `\;` and `+` at the end of a find command?

These are bash commands that are used to convert tabs to spaces.
Here's the link to the original stackoverflow post.
This one uses \; at the end of the command
find /path/to/directory -type f -iname '*.js' -exec sed -ie 's|\t| |g' '{}' \;
This one uses + instead of \;.
find /path/to/directory -type f -iname '*.js' -exec sed -ie 's|\t| |g' '{}' '+'
What exactly is the difference between the two?
The \; or + is not related to bash. It's an argument to the find command, specifically to find's -exec option.
find -exec uses {} to pass the current file name to the specified command, and \; to mark the end of the the command's arguments. The \ is needed because ; by itself is special to bash; by typing \;, you can pass a literal ; character as an argument. (You can also type ';' or ";".)
The + symbol (no \ needed because + is not special to bash) causes find to invoke the specified command with multiple arguments rather than just once, in a manner similar to xargs.
For example, suppose the current directory contains 2 files named abc and xyz. If you type:
find . -type f -exec echo {} \;
it invokes the echo command twice, producing this output:
./abc
./xyz
If you instead type:
find . -type f -exec echo {} +
then find invokes echo just once, with the following output:
./xyz ./abc
For more information, type info find or man find (if the documentation is installed on your system), or you can read the manual online at http://www.gnu.org/software/findutils/manual/html_node/find_html/

Resources