2

I have two files, let's call them foo.py and fixed_rule_map. I need to:

  • find the line in foo.py that contains the pattern rule_map = . It would be enough to find the pattern rule_map, if that's easier, but since there are more than one line in foo.py which contains rule_map, in this case I need to match only the first occurrence.
  • substitute the line in foo.py with the contents of fix_rule_map_in_foo (which contains a single line of text).

For example, foo.py may look like this:

class example:
    """some useless text.
    
    """
    rule_map = {'greater': lambda x, y: x > y, 'less': lambda x, y: x < y}

    def __init__(self,
                rule=None,
                ):
        self._init_rule(rule)


    def _init_rule(self, rule):
        if rule not in self.rule_map and rule is not None:
            raise KeyError(f'rule must be greater, less or None, '
                           f'but got {rule}.')
        self.rule = rule
        if self.rule is not None:
            self.compare_func = self.rule_map[self.rule]

and I would like to correct it to this:

class example:
    """some useless text.
    
    """
    rule_map = {'greater': lambda x, y: (x if x is not None else -inf) > (y if y is not None else -inf), 'less': lambda x, y: (x if x is not None else -inf) < (y if y is not None else -inf)}

    def __init__(self,
                rule=None,
                ):
        self._init_rule(rule)


    def _init_rule(self, rule):
        if rule not in self.rule_map and rule is not None:
            raise KeyError(f'rule must be greater, less or None, '
                           f'but got {rule}.')
        self.rule = rule
        if self.rule is not None:
            self.compare_func = self.rule_map[self.rule]

thus fixed_rule_map would be a text file containing

    rule_map = {'greater': lambda x, y: (x if x is not None else -inf) > (y if y is not None else -inf), 'less': lambda x, y: (x if x is not None else -inf) < (y if y is not None else -inf)}

Note that since foo.py is a Python file, the substitution must preserve indentation.

The command that performs the substitution has to go in a Dockerfile, since the substitution must be performed when building the Docker image. Docker uses /bin/sh as an interpreter for RUN commands, rather than /bin/bash, which may be a problem if your solution uses bash. However, I think I could fix this by using a shell script fix_rule_map_in_foo.sh:

#! /bin/bash
FOO_PATH = /path/to/foo
<your_solution>

and adding this line in the Dockerfile:

RUN fix_rule_map_in_foo.sh
2
  • Something like this awk -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py
    – Raffa
    Nov 28, 2022 at 12:34
  • 1
    @Raffa sounds interesting. Can you write it as an answer and explain in detail the syntax of the command?
    – DeltaIV
    Nov 28, 2022 at 14:13

2 Answers 2

2

You can set that single line contained in the file fix_rule_map_in_foo in a variable named e.g. fix with command substitution like so:

fix="$(cat fix_rule_map_in_foo)"

You can set the variable to a fixed string as well if you wish (instead of reading that string from a file) like so:

fix="The substitution string here"

Then pas that variable to awk so you can match the line and substitute it like so:

awk -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py

That will search for rule_map = and replace the line that contains the match with the value of the variable fix and print all the lines(after modification) to the terminal ... you can redirect the output to a file if you wish with e.g. >.

If you wish to edit the file foo.py instead, then use the GNU awk like so:

gawk -i inplace -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py 
5
  • The Docker image I'm using doesn't have gawk, but only awk. Isn't it possible to edit the file foo.py using awk?
    – DeltaIV
    Nov 28, 2022 at 14:46
  • Also, I made some edits to the question. Could you please have a look? Thanks. It's important that the substitution preserves the indentation, since foo.py is a Python script and indentation matters.
    – DeltaIV
    Nov 28, 2022 at 14:47
  • 1
    @DeltaIV Not natively but you can ether use command substitution like echo $(awk -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py) > foo.py or ...
    – Raffa
    Nov 28, 2022 at 14:55
  • 1
    Write to a temp file then rename it overwriting the original file awk -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py > tmpfile && mv tmpfile foo.py ...
    – Raffa
    Nov 28, 2022 at 15:00
  • 1
    That said some versions of awk do that so test first awk -i inplace -v fix="$(cat fix_rule_map_in_foo)" '/rule_map = / {$0 = fix}1' foo.py ... might work.
    – Raffa
    Nov 28, 2022 at 15:01
2

You can use the sed editor's r command to read the contents of a file and insert them, deleting the original matching line after:

sed -e '/rule_map = /{r fix_rule_map_in_foo' -e 'd;}' foo.py

The new contents will be written to standard output by default, but GNU sed supports an in-place editing flag (--in-place or -i).

Since this doesn't involve any shell constructs, I think you can run it directly (although I don't use docker, so don't know for sure):

RUN sed -i.bak -e '/rule_map = /{r fix_rule_map_in_foo' -e 'd;}' foo.py

however you can put it in a POSIX shell script if you prefer.

Alternatively, using the non-stream editor ed (this does use shell constructs, so must be in a script):

#!/bin/sh

printf '%s\n' '/rule_map = /r fix_rule_map_in_foo' '-1d' 'wq' | ed -s foo.py
2
  • why sed -i.bak? Shoudn't the command be sed -i?
    – DeltaIV
    Nov 28, 2022 at 14:58
  • it looks like sed in my Docker image supports the -i option, so this should work.
    – DeltaIV
    Nov 28, 2022 at 14:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.