Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to escape commands passed to find with the exec option on Linux

Let me break down my problem into the simplest example I can.

Create a test file containing one line of text.

[root@myserver ] /tmp> echo "test ReplaceMe DoNotReplaceMe" > /tmp/daj.txt

We have an existing find command that we use to substitute text in all the files that match it (in this example I've simplified this command to only work on one file, and stripped out the other stuff it does).

The problem is that it substitutes "ReplaceMe" everywhere it appears, instead of only when it is a word on its own.

[root@myserver ] /tmp> find /tmp/daj.txt -exec sh -c 'f="{}"; sed -e 's/ReplaceMe/#DONE#/gi' "${f#.}" ' \;
test #DONE# DoNot#DONE#

I've written a new sed command to only substitute "ReplaceMe" when it is a word on its own, but NOT when it is a substring of another word. The output from this command is correct.

[root@myserver ] /tmp> cat /tmp/daj.txt | sed -e 's/\(\W\)\(ReplaceMe\)\(\W\)/\1#DONE#\3/gi'    
test #DONE# DoNotReplaceMe

When I try to incorporate the updated sed command into the find command, it breaks. It looks like I am hitting an escaping problem, but I haven't managed to solve it by adding extra escaping.

[root@myserver ] /tmp> find /tmp/daj.txt -exec sh -c 'f="{}"; sed -e 's/\(\W\)\(ReplaceMe\)\(\W\)/\1#DONE#\3/gi' "${f#.}" ' \;
sh: -c: line 0: syntax error near unexpected token `('
sh: -c: line 0: `f="/tmp/daj.txt"; sed -e s/(W)(ReplaceMe)(W)/1#DONE#3/gi "${f#.}" '

Is there a way to escape my sed command so that I can run it via find, or do I have to look for an alternative solution?

Update: The full find command we are running prints out the filename and permissions, and then pipes the output of the sed to md5sum. Here's an example of it running and matching multiple files:

[root@myserver ] ~> find /tmp -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c 'f="{}"; sed  -e 's/ReplaceMe/#DONE#/gi' "${f#.}" | md5sum' \;
/tmp/daj2.txt 644 d52bbd311552234b761bcae694c2055a  -
/tmp/daj.txt 644 d52bbd311552234b761bcae694c2055a  -
like image 340
Dan J Avatar asked Jan 06 '11 00:01

Dan J


People also ask

What is exec in find command in Linux?

The best feature of the find command is its exec argument that allows the Linux user to carry out any command on the files that are found. In other words, actions can be performed on the files that are found.

What is exec $@ in Linux?

exec "$@" is typically used to make the entrypoint a pass through that then runs the docker command. It will replace the current running shell with the command that "$@" is pointing to. By default, that variable points to the command line arguments.

What is exec command?

The exec command is a powerful tool for manipulating file-descriptors (FD), creating output and error logging within scripts with a minimal change. In Linux, by default, file descriptor 0 is stdin (the standard input), 1 is stdout (the standard output), and 2 is stderr (the standard error).

How does exec work in Linux?

exec command in Linux is used to execute a command from the bash itself. This command does not create a new process it just replaces the bash with the command to be executed. If the exec command is successful, it does not return to the calling process.


2 Answers

You should not be using {} directly in the shell, instead you should be passing the file names in as shell parameters. Also, if you want to limit to whole-word matches then use \<word\> for sed

Update

find /tmp -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c "sed  -e 's/\<ReplaceMe\>/#DONE#/gi' \$@ | md5sum" _ {} \;

Output

$ find . -regex '.*daj.*\.txt' -printf '%p %m ' -exec sh -c "sed  -e 's/\<ReplaceMe\>/#DONE#/gi' \$@ | md5sum" _ {} \;
./daj2.txt 664 ea324b4721ed037dbc2402ded4446005  -
./daj.txt 664 0bbb9104da99c1c1187a2a35e6ac0e9b  -
like image 176
SiegeX Avatar answered Jan 02 '23 12:01

SiegeX


This doesn't answer your question about the escape sequence but does solve the problem. I'd basically use xargs with sed like this:

$ find ~/tmp/data.txt | xargs sed -e 's/\<replaceme\>/1234/'
1234 in this sentance
donotreplaceme in this sentance
$ 

and the contents of data.txt:

replaceme in this sentance
donotreplaceme in this sentance

Also if you might have filenames with spaces in it using the -print0 parameter tells find to output the list of files as null terminated strings. Otherwise find will interpret the space in the filename as the end of the the filename. Then when using xargs you need to use the -0 parameter to tell xargs that the input is a list of null terminated strings. Example below:

find /somedir -print0 | xargs -0 command
like image 32
sashang Avatar answered Jan 02 '23 12:01

sashang