letzte Aktualisierung: 21. März 2007
default ] [ fancy ]

Brainstormed random thoughts

As I currently don't have the time to think about a concept for this documentation page I'll just write down what comes to my mind right now and add stuff as I stumble upon it.

Unix Commands






Parameters I use to update this homepage from my computer at home

$ rsync --rsh=ssh --recursive --verbose --progress --cvs-exclude --update /var/www/loisch/build loisch.de:~/www/


  __ _       _      _     _                       _
 / _(_) __ _| | ___| |_  (_)___    ___ ___   ___ | |
| |_| |/ _` | |/ _ \ __| | / __|  / __/ _ \ / _ \| |
|  _| | (_| | |  __/ |_  | \__ \ | (_| (_) | (_) | |
|_| |_|\__, |_|\___|\__| |_|___/  \___\___/ \___/|_|

Do I have to say more? Yes? Ok, there you go...


Redirection and command substition with exec

Have you ever tried something like
$ find -name "*.java" -exec sed -e "s/foo/bar/" {} > {} \;
to replace all occurences of foo with bar in all java files and wondered why it does not work? Probably you figured out that it is the redirection that is causing the problem. This redirection is applied to the find command as a whole and thus redirects the standard output from find program to the file {}. What you intended was to redirect the output of sed applied to all found files to the respective file found, the file read be sed. But how can we accomplish that?
But wait... if the redirection was applied before the command line parameters could be evaluated by
how could find possible find the escaped semicolon that follows the redirection? Altough commonly used at the end of the command the shell does not require them to be used there. A redirection could also be placed directly after the command name, for example $ echo > foo "Bar"
So back to the find problem. It's clear we need to somehow escape the redirection symbol so it is not interpreted by the top-level shell. Let's try to quote the entire command using single quotes (which means that the shell will not apply shell expansions in the quoted text and treat it as a single command line argument):
$ find -name "*.java" -exec 'sed -e "s/foo/bar/" {} > {}' \;
find: sed -e "s/foo/bar/" ./t1.java > ./t1.java: No such file or directory
It doesn't work. But why does find complain not finding sed? Well... because it isn't looking for a command named sed for for one named
sed -e "s/foo/bar/" ./t1.java > ./t1.java
. Ups. ;-) Maybe we shouldn't include sed in the quotes. Let's give it a try:
$ find -name "*.java" -exec 'sed -e "s/foo/bar/" {} > {}' \;
sed: -e expression #1, char 2: Unknown command: ``"''
Now the damn double quotes of our sed expression get passed to sed. Before they just had the purpose of making the expression a single command line argument possibly consisting of whitespace characters and other characters otherwise interpreted by the shell. We won't find a solution going on like this...
The simplest solution is to just execute a shell using exec and let that shell do the redirection:
Don't try this at home!
$ find -name "*.java" -exec sh -c 'sed -e "s/foo/bar/" {} > {}' \;
Ahhhhh! We got no errors... but what happend? All files are empty! Why? Well... your intelligent bash detects that the file you want to redirect the command output to already exists and clears it. After all don't want to append to it. To avoid such desasters consider activating the no-clobber option (
set -o noclobber
). It will no get passed to subshells. Add it to your ~/.profile to permanently set it for interactive and non-interactive shells.
So, what's the real version? Here it is! :-)
$ find -name "*.java" -exec sh -c 'sed -e "s/foo/bar/" {} > .tmp; cp .tmp {}; rm .tmp' \;