What's the Mac OS X way to execute this command - macos

I am trying to execute this command:
find ./ -type f -readable -writable -exec sed -i "s/A/B/g" {} \;
and I'm getting this error:
find: -readable: unknown primary or operator
I am trying to find and replace extensively, anything with the name 'A' to 'B', this will apply to file names and text inside of files.
What's the best way for me to execute this on Mac terminal?

The BSD version of find doesn't have the -readable or -writable primaries, but you can fake them using the test command:
find ./ -type f -exec test -r {} -a -w {} \; -exec sed -i "" "s/A/B/g" {} \;
There's a possible problem here in that test's syntax can be ambiguous with more than three arguments, so a compound test expression like this might be misparsed. I don't think this can be a problem with paths that begin with ./, but if you're worried about it you can use two separate tests:
find ./ -type f -exec test -r {} \; -exec -w {} \; -exec sed -i "" "s/A/B/g" {} \;
Also, note that I added a null argument to sed after -i. This is another BSD-vs-GNU thing. The BSD version of sed requires an argument (the extension to use for a backup) to the -i option, so in order to avoid making a backup you have to explicitly supply a blank. The GNU version, on the other hand, would be confused by sed -i .bak because it requires the argument to -i be directly attached to it (sed -i.bak). Wheee.

Related

Why can't I pipe find results into cp, but it works using exec?

I am using find to list files within multiple directories with a specific extension. I tried
find /path/to/encompassing/directory/ -d -name "*modified.tif" | xargs cp Destination_Directory/
but it didn't work. Using
find /path/ -d -name "*modified.tif" -type f -exec cp {} Destination_Directory \;
works but I don't understand why xargs isn't working.
If you write
find -name '*modified.tif' | xargs cp directory
then that's the same as writing
cp directory file1modified.tif file2modified.tif
(or whatever filenames matched), which is the wrong way around, because xargs by default appends arguments.
find -name '*modified.tif' -exec cp {} directory \;
is the same as
cp file1modified.tif directory
cp file2modified.tif directory
which is what you want.
You can achieve the same with xargs by using
xargs -I{} cp {} directory
to specify where in the command you want to use the argument, but that implies that only one file at a time will be copied (because -I implies -L1).
To avoid calling cp once per file, you can use the -t option for cp so the files to be copied can be appended to the end of the command (requires GNU cp):
find -name '*modified.tif' | xargs cp -t directory
which is equivalent to
cp -t directory file1modified.tif file2modified.tif
or better, taking care of blanks in filenames,
find -name '*modified.tif' -print0 | xargs -0 cp -t directory
Alternatively, without xargs:
find -name '*modified.tif' -exec cp -t directory {} +
where -exec {} + makes sure to invoke cp as few times as possible.
xargs passes each word from its standard input as the last argument to cp, not the first. As a result, you are trying to run the series of commands
cp Destination_Directory/ foo
cp Destination_Directory/ bar
# etc
If you are using GNU cp, you can fix this simply by using the -t option to specify that Destination_Directory is the target, rather than a source.
... | xargs cp -t Destination_Directory
# cp -t Destination_Directory foo
# cp -t Destination_Directory bar
# etc
You might be able to use the -I option in xargs to make it use the incoming file name as the first argument:
... | xargs -I '{}' cp '{}' Destination_Directory
however, this makes a lot of assumptions about the names find will produce. (No leading or trailing whitespace, and no newlines in the file names.) (For that matter, xargs without -I is treating each whitespace-delimited word from its input as a separate argument for a call to cp.) In general, you should not try to use the output of find programmatically. Stick with its -exec primary instead.
Your code
find /path/ -d -name "*modified.tif" -type f -exec cp {} Destination_Directory \;
is the right way to go. No shell is involved, so each file name is passed as is as the first argument to cp.
I don't use xargs, but I think it should work like this :
cp `find /path/to/encompassing/directory/ -d -name "*modified.tif"` Destination_Directory/
No need for a pipe then.

For loop for result of "find" command

I have this find command:
$ find . -name '*.jar' -exec grep -Hls BuildConfig {} \;
I need to execute this command for each result of command find above:
$ zip -d RESULTPATH "*/BuildConfig.class"
So can I do this with for loop or not? Can I create .sh file that will be doing what I need?
Thanks!
A clean way to run a command for each result of a grep command with xargs, is using the -Z or --null flag with grep to make the results terminated by null, and the -0 flag with xargs so that it expects values terminated by null, like this:
find . -name '*.jar' -exec grep -Z BuildConfig {} \; | xargs -0 zip -d RESULTPATH "*/BuildConfig.class
I removed the Hls flags because they all seem pointless (even harmful) in your use case.
But I'm afraid this will not actually work for your case,
because a .jar file is usually a binary file (zipped archive of Java classes),
so I don't think the grep will ever match anything. You can give zgrep a try to search inside the jars.
The grep command has two channels for information out of it. The first and most obvious one is of course stdout, where it sends matches it finds. But if it finds no matches, it also uses an exit value > 0 to inform you. Combined with the -q (quiet) option, you can use this as a more intelligent option for find:
$ find . -name '*.jar' -exec zgrep -sq BuildConfig {} \; -exec zip -d {} "*/BuildConfig.class" \;
This assumes that you want to search through the compressed file using grep -Z, of course. :)
Or for easier reading:
find . -name '*.jar' \
-exec zgrep -sq BuildConfig {} \; \
-exec zip -d {} "*/BuildConfig.class" \;
Find operates by running each test in order. You can think of the options as a series of filters; the -name option is the first filters, and a file only gets passed to the second -exec if the preceding -exec exited without errors.

issue with piping find into sed (find and replace)

Here is my current code, my goal is to find every file in a given directory (recursively) and replace "FIND" with "REPLACEWITH" and overwrite the files.
FIND='ALEX'
REPLACEWITH='<strong>ALEX</strong>'
DIRECTORY='/some/directory/'
find $DIRECTORY -type f -name "*.html" -print0 |
LANG=C xargs -0 sed -i "s|$FIND|$REPLACEWITH|g"
The error I am getting is:
sed: 1: "/some/directory ...": command a expects \ followed by text
As given in BashFAQ #21, you can use perl to perform search-and-replace operations with no potential for data being treated as code:
in="$FIND" out="$REPLACEWITH" find "$DIRECTORY" -type f -name '*.html' \
-exec perl -pi -e 's/\Q$ENV{"in"}/$ENV{"out"}/g' '{}' +
If you want to include only files matching the FIND string, find can be told to only pass files which grep flags on to perl:
in="$FIND" out="$REPLACEWITH" find "$DIRECTORY" -type f -name '*.html' \
-exec grep -F -q -e "$FIND" '{}' ';' \
-exec perl -pi -e 's/\Q$ENV{"in"}/$ENV{"out"}/g' '{}' +
Because grep is being used to evaluate individual files, it's necessary to use one grep call per file so its exit status can be evaluated on a per-file basis; thus, the use of the less efficient -exec ... {} ';' action. For perl, it's possible to put multiple files to process on one command, hence the use of -exec ... {} +.
Note that fgrep is line-oriented; if your FIND string contains multiple lines, then files with any one of those lines will be passed to perl for replacements.
You can have find invoke sed directly although I think all the modification times on your files will be affected (which might matter or not):
find $DIRECTORY -type f -name "*.html" -exec sed -i "s|$FIND|$REPLACEWITH|g" '{}' ';'

Awk/Sed: How to do a recursive find/replace of a string in files with a certain file extension?

I need to recursively find and replace a string in my .cpp and .hpp files.
Looking at an answer to this question I've found the following command:
find /home/www -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'
Changing it to include my file type did not work - did not changed any single word:
find /myprojects -type f -name *.cpp -print0 | xargs -0 sed -i 's/previousword/newword/g'
Help appreciated.
Don't bother with xargs; use the -exec primary. (Split across two lines for readability.)
find /home/www -type f -name '*.cpp' \
-exec sed -i 's/previousword/newword/g' '{}' \;
chepner's helpful answer proposes the simpler and more efficient use of find's -exec action instead of piping to xargs.
Unless special xargs features are needed, this change is always worth making, and maps to xargs features as follows:
find ... -exec ... {} \; is equivalent to find ... -print0 | xargs -0 -n 1 ...
find ... -exec ... {} + is equivalent to find ... -print0 | xargs -0 ...
In other words:
the \; terminator invokes the target command once for each matching file/folder.
the + terminator invokes the target command once overall, supplying all matching file/folder paths as a single list of arguments.
Multiple calls happen only if the resulting command line becomes too long, which is rare, especially on Linux, where getconf ARG_MAX, the max. command-line length, is large.
Troubleshooting the OP's command:
Since the OP's xargs command passes all matching file paths at once - and per xargs defaults at the end of the command line, the resulting command will effectively look something like this:
sed -i 's/previousword/newword/g' /myprojects/file1.cpp /myprojects/file2.cpp ...
This can easily be verified by prepending echo to sed - though (conceptual) quoting of arguments that need it (paths with, e.g., embedded spaces) will not show (note the echo):
find /myprojects -type f -name '*.cpp' -print0 |
xargs -0 echo sed -i 's/previousword/newword/g'
Next, after running the actual command, check whether the last-modified date of the files has changed using stat:
If they have, yet the contents haven't changed, the implication is that sed has processed the files, but the regex in the s function call didn't match anything.
It is conceivable that older GNU sed versions don't work properly when combining -i (in-place editing) with multiple file operands (though I couldn't find anything in the GNU sed release notes).
To rule that out, invoke sed once for each file:
If you still want to use xargs, add -n 1:
find /myprojects -type f -name '*.cpp' -print0 |
xargs -0 -n 1 sed -i 's/previousword/newword/g'
To use find's -exec action, see chepner's answer.
With a GNU sed version that does support updating of multiple files with the -i option - which is the case as of at least v4.2.2 - the best formulation of your command is (note the quoted *.cpp argument to prevent premature expansion by the shell, and the use of terminator + to only invoke sed once):
find /myprojects -type f -name '*.cpp' -exec sed -i 's/previousword/newword/g' '{}' +

integrate sed into find xargs copy shell command

I have a script that I use to copy all of the files in one folder and move them to a new folder... the line i use to do it looks like this
find "$SOURCEFOLDER" -type f | xargs -I {} ln {} "$ENDFOLDER/$TR_NEW_TORRENT_NAME/${basename}"
and it works perfectly
the thing is I'd like to also use sed to remove any brackets from the basename of the new file but i don't know how to incorporate the sed command
sed -e 's/\[[^][]*\]//g' FILE
how would I go about doing this? Is there a better or simpler way to do all the things I want?
I believe following will work for you:
find "$SOURCEFOLDER" -type f -exec bash -c "sed -e 's/\[[^][]*\]//g' {} ; xargs -I {} ln {} "$ENDFOLDER/$TR_NEW_TORRENT_NAME/${basename}" \;
Idea is to combine the commands this ways:
another way is to use two -exec
find "$SOURCEFOLDER" -type f -exec sed -e 's/\[[^][]*\]//g' {}\; -exec ln {} "$ENDFOLDER/$TR_NEW_TORRENT_NAME/${basename}" \;
I hope this will help.
You can use -execdir option of find for this renaming and avoid xargs altogether:
find "$SOURCEFOLDER" -type f -execdir bash -c 'sed "s/\[[^][]*\]//g" <<< "$1";
ln "$1" "$ENDFOLDER/$TR_NEW_TORRENT_NAME/${basename}"' - '{}' \;

Resources