3

I want to search each record (records are defined by blank lines) in a file for the pattern NAME#AAAA. If it matches, then insert an # in front of the record's AGE line and move that line to the top of the paragraph. Then insert the line AGE NIL at the end:

INPUT FILE:

NAME#AAAA
STD 1
SEC A
AGE 5

NAME#BBBB
STD 2
SEC B
AGE 6


NAME#CCCC
STD 3
SEC C
AGE 7

NAME#AAAA
STD 4
AGE 9


NAME#AAAA
STD 7
SEC A
AGE 12

EXPECTED OUTPUT

#AGE 5
NAME#AAAA
STD 1
SEC A
AGE NIL

NAME#BBBB
STD 2
SEC B
AGE 6

NAME#CCCC
STD 3
SEC C
AGE 7

#AGE 9
NAME#AAAA
STD 4
AGE NIL

#AGE 12
NAME#AAAA
STD 7
SEC A
AGE NIL

Also, I need the inverse of it. Just to revert the back the changes done. Please note that I am doing all this on an AIX machine.

  • Please use the formatting tools to format your posts clearly. Also, are you using AIX? If so, please mention that explicitly in your question since that limits the tools we can suggest. – terdon Apr 19 '16 at 16:30
  • oh :( ..Yes. I'm using AIX. – user4432340 Apr 19 '16 at 16:32
  • Is this possible? – user4432340 Apr 19 '16 at 18:44
3

This is a perfect use case for ex, the POSIX-specified tool of choice for file editing.

(If you've ever used vi, by the way, you are likely familiar with ex since everything you type in vi that starts with a colon : is an ex command. ex is the predecessor of vi.)

printf %s\\n 'g/NAME#AAAA/ /AGE/t- | s/^/#/ | /AGE/s/.*/AGE NIL/' x | ex input.txt

If you want to test it before you actually save the file, change the final x before the pipe symbol into %p and the modified file won't be saved, but the modified version will be printed to stdout. So here is the testing command:

printf %s\\n 'g/NAME#AAAA/ /AGE/t- | s/^/#/ | /AGE/s/.*/AGE NIL/' %p | ex input.txt

Explanation:

printf %s\\n provides an easy way to feed multiple commands to ex with a newline after each.

g/regex/ is the global command; it runs the commands which follow (up to the next newline) on each line that matches the given regex.

/AGE/t- copies the next line which matches the pattern /AGE/ to a position just before the current line (which is the NAME#AAAA line). It also moves the cursor to the new copy of the line (so that now becomes the "current line").

| is a command separator in ex.

s/^/#/ prefixes the copied AGE line with a hashtag. (Or a pound sign, depending on your dialect.) ;)

The next command really has two parts: /AGE/ is the address, which makes this command operate on the next line which contains that pattern, and s/.*/AGE NIL/ replaces whatever that line was, with AGE NIL.

x saves the changes to the file and exits.


Reversing the changes

To reverse the changes, I would do the following:

printf %s\\n 'g/NAME#AAAA/ ?^#AGE? m /^AGE/ | s/^#// | -d' %p | ex input.txt

Then when the change was verified, actually save the changes with:

printf %s\\n 'g/NAME#AAAA/ ?^#AGE? m /^AGE/ | s/^#// | -d' x | ex input.txt

Explanation:

Global command as before.

Take the line that starts with #AGE from before the NAME line, move it after the next line that starts with AGE.

Remove the leading #.

Delete the immediately preceding line with -d (which is the NIL age line).

Print or save changes.

1
perl -pe 'BEGIN{$/=""} 
          s/^(NAME#AAAA.*\n)(AGE.*?)(\n+)$/#$2\n$1AGE NIL$3/s' ex1

very brief explanations:

For all the registers in input                 |  perl -p
   separator= one or more empty lines          |     BEGIN{$/=""}
do:
  | substitute                                 |     s/
  |   ^(NAME AAAA.*\n)(AGE.*?)(\n+)$           |       regex /
  |    1              2       3                | 
  | by                                         |      /subst. string including
  |   # $2 \n  $1   AGE NIL   $3               |       capture groups/
  |                                            |
  | and print                                  |  ...from option -p

Update:

Is if possible to have variables in place of NAME#AAAA ?

perl -pe '
    BEGIN{
       $/=""; 
       $f=shift;  }
    s/^(NAME#$f.*\n)(AGE.*?)(\n+)$/#$2\n$1AGE NIL$3/s' AAAA  ex1

In this version we must provide a pattern argument (Eg: "AAAA"):

  • line 4 : get first argument from the command line ("AAAA") and store it in $f
  • line 5 : expand $f in the substitute pattern.
  • 1
    This would really be improved with an explanation of the pieces, even if very succinct. Would you mind adding that? :) – Wildcard Apr 19 '16 at 19:17
  • @Wildcard, lets do it! (sorry to be so lazy); brief explanation added. – JJoao Apr 19 '16 at 21:09
  • Is if possible to have variables in place of NAME#AAAA like printf %s\\n 'g/$line/ /AGE/t- | s/^/#/ | /AGE/s/.*/AGE NIL/' %p | ex input.txt – user4432340 Apr 20 '16 at 8:27
  • 1
    @user4432340, so... printf %s\\n 'g/$line/ /AGE/t- | s/^/#/ | /AGE/s/.*/AGE NIL/' %p | ex input.txt will fail but printf %s\\n 'g/'$line'/ /AGE/t- | s/^/#/ | /AGE/s/.*/AGE NIL/' %p | ex input.txt will have interpolation – JJoao Apr 20 '16 at 11:12
  • 1
    @JJoao, also note that if you're embedding a variable in the code, (a) you should put it in double quotes, e.g. 'g/'"$line"'/ /AGE/... to avoid whitespace splitting by the shell following variable expansion and (b) realize it will be handled as a regex, not as a fixed string (so any periods or asterisks may not do what you expect) and (c) ensure you don't have any / characters in the $line or else the characters after them will be taken as ex commands. – Wildcard Apr 20 '16 at 21:05

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.