skip to Main Content

I am finding a bash command for a conditional replacement with offset. The existing posts that I’ve found are conditional replacement without offset or with a fixed offset.

Task: If uid contains 8964, then insert the line FORBIDDEN before DOB.

Each TXT file below represents one user, and it contains (in the following order)

  1. some property(ies)
  2. unique uid
  3. some quality(ies)
  4. unique DOB
  5. a random lorem ipsum

I hope I can transform the following files

# file1.txt (uid doens't match 8964)
admin: false
uid: 123456
happy
movie
DOB: 6543-02-10
lorem ipsum
seo varis lireccuni paccem noba sako

# file2.txt (uid matches 8964)
citizen: true
hasSEAcct: true
uid: 289641
joyful hearty
final debug Juno XYus
magazine
DOB: 1234-05-06
saadi torem lopez dupont

into

# file1.txt (uid doens't match 8964)
admin: false
uid: 123456
happy
movie
DOB: 6543-02-10
lorem ipsum
seo varis lireccuni paccem noba sako

# file2.txt (uid matches 8964)
citizen: true
hasSEAcct: true
uid: 289641
joyful hearty
final debug Juno XYus
magazine
FORBIDDEN
DOB: 1234-05-06
saadi torem lopez dupont

My try:

If uid contains 8964, then do a 2nd match with DOB, and insert FORBIDDEN above DOB.

sed '/^uid: [0-9]*8964[0-9]*$/{n;/^DOB: .*$/{iFORBIDDEN}}' file*.txt

This gives me an unmatched { error.

sed: -e expression #1, char 0: unmatched `{'

I know that sed '/PAT/{n;p}' will execute {n;p} if PAT is matched, but it seems impossible to put /PAT2/{iTEXT} inside /PAT/{ }.

How can I perform such FORBIDDEN insertion?

3

Answers


  1. $ awk '
      /^uid/ && /8964/ {f=1}                     #1
      /^DOB/ && f {print "FORBIDDEN"; f=0}       #2
      1                                          #3
    ' file
    
    1. If a line starting with “uid” matches “8964”, set flag
    2. If a line starts with “DOB” and flag is set, print string and unset flag
    3. print every line
    $ awk -v RS='' '/uid: [0-9]*8964/{sub(/DOB/, "FORBIDDENnDOB")} 1' file
    

    Alternatively, treat every block separated by a blank line as a single record, then sub in “FORBIDDENnDOB” if there’s a match. I think the first one’s better practice. As a very general rule, once you start thinking in terms of fields/records, it’s time for awk/perl.

    Login or Signup to reply.
  2. In my opinion, this is a good use-case for sed.

    Here is a GNU sed solution with some explanation:

    # script.sed
    /^uid:.*8964/,/DOB/ { # Search only inside this range, if it exists.
      /DOB/i FORBIDDEN    # Insert FORBIDDEN before the line matching /DOB/.
    }
    

    Testing:

    ▶ gsed -f script.sed FILE2 
    citizen: true
    hasSEAcct: true
    uid: 289641
    joyful hearty
    final debug Juno XYus
    magazine
    FORBIDDEN
    DOB: 1234-05-06
    saadi torem lopez dupont
    
    ▶ gsed -f script.sed FILE1
    admin: false
    uid: 123456
    happy
    movie
    DOB: 6543-02-10
    lorem ipsum
    seo varis lireccuni paccem noba sako
    

    Or on one line:

    ▶ gsed -e '/^uid:.*8964/,/DOB/{/DOB/i FORBIDDEN' -e '}' FILE*
    
    Login or Signup to reply.
  3. tried on gnu sed

     sed -Ee '/^uid:s*w*8964w*$/{n;/^DOB:/iFORBIDDEN' -e '}' file*.txt
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search