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
#!/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/' {} +
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.
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!
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.
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/