nikitakit
nikitakit

Reputation: 183

Shell script: Replace matching text with output of program

I want to replace specific strings in a text stream with the output of a program run on those strings. For example, replace any occurrence of "#filename#" with the output of identify filename

Is there any easy way to do this?

Upvotes: 1

Views: 1722

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753970

Assuming 'identify filename' is itself a command, then I think you'll need to use Perl for this. In the best Perl inscrutable style:

while (<>)
{
     s/#(\w+)#/my $x = qx%identify $1%; chomp $x; $x/e;
     print;
}

This reads a line of input ('<>') into the implicit variable '$_'; the next line applies a substitute operation to the implicit variable - more details in a moment - and then the 'print' prints the implicit variable.

As to the substitute operation 's///', the first part looks for a hash '#', a series of one or more 'word' characters - alphanumerics or underscore - and another hash, making the identified file name available as '$1'. The second part is the replacement string. After the third slash is the modifier 'e' which means 'execute the replacement as bit of Perl'. And the relevant bit of Perl is:

my $x = qx%identify $1%; chomp $x; $x

The first part executes the command 'identify filename' if the string between the hash marks is 'filename', saving the output, newline and all, in local variable $x. The 'chomp' operation removes the newline; the final '$x' yields a value - the string that was output by the 'identify' command. (Somewhat to my surprise, Perl does not allow a simpler looking: s/#(\w+)#/chomp qx%identify $1%/e; the error was 'Can't modify quoted execution (``, qx) in chomp at xx.pl line 3, near "qx%identify $1%)"'.)

Consider the 'identify' command:

echo "identified file $1 as $PWD/$1"

Now consider the input line:

abc#def#ghi

The output is:

abcidentified file def as /Users/jleffler/tmp/soq/defghi

(where /Users/jleffler/tmp/soq happened to be my current directory while running the command).

Rather less inscrutably:

while (my $line = <>)
{
    if ($line =~ m/#(\w+)#/)
    {
        my $identity = qx{identify $1};
        chomp $identity;
        $line =~ s/#\w+#/$identity/;
    }
    print $line;
}

Certainly not as compact, but the explanation is very similar.

Note that the initial edition is not the most compact form possible. Consider this version:

perl -p -e 's/#(\w+)#/my $x = qx%identify $1%; chomp $x; $x/e'

The '-p' option places the script (the argument to '-e') in a read, execute, print loop (REPL).

That's one of the wonders of Perl - TMTOWTDI (pronounced 'tim-toady') - There's More Than One Way To Do It.

Upvotes: 3

Wesley Rice
Wesley Rice

Reputation: 2831

REPLACEMENT=`identify filename`
sed "s/#filename#/$REPLACEMENT/g"

EDIT: See Dennis Williamson's comment.

Upvotes: 0

Related Questions