1

I have a report (simple text file) that needs to be updated (via bash) for service consumption.

The report consists of lists of categories each with a list of items, something like the following example:

vehicle:
car
moto
done

fruit:
banana
apple
done

tree:
pine
oak
done

The purpose of the update is to add new items after the last of a specific category:

Add orange to fruit after apple

For this I've created a small sample script to test/debug this procedure/update using sed:

#!/bin/bash

file=report.txt

cat > "$file" << EOT
vehicle:
car
moto
done

fruit:
banana
apple
done

tree:
pine
oak
done
EOT

category="fruit"
append="orange"

## only $category has $append - but at start:
#find_lhs="^$category:"
#replace_rhs="$category:\n$append"

## all categories have $append at end:
#find_lhs="([\S\s]*?)done"
#replace_rhs="\1$append\ndone"

# add $append at the end of $category - nothing happens:
find_lhs="^$category:([\S\s]*?)done"
replace_rhs="$category:\n\1$append\ndone"

sed -i -E "s/$find_lhs/$replace_rhs/g" "$file"

cat "$file"

Question:

What is missing/failing with:

sed -i -E "s/^$category:([\S\s]*?)done/$category:\n\1\n$append\ndone/g" "$file"

to produce the following report content:

vehicle:
car
moto
done

fruit:
banana
apple
orange #<-- inserted
done

tree:
pine
oak
done

??


Disclaimers:

mo7ty
  • 159
  • 10

3 Answers3

1

Using sed

$ sed "/$category/,/done/{/done/s/^/$append\n/}" <<< "$file"
$ sed "/$category/{:a;/done/s/^/$append\n/;n;/^$/!ba}" file
$ sed -e "/$category/{:a;/done/i$append" -e 'n;/^$/!ba}' file

Output

vehicle:
car
moto
done

fruit:
banana
apple
orange
done

tree:
pine
oak
done
HatLess
  • 10,622
  • 5
  • 14
  • 32
  • tested on Ubuntu 20.04 and the file didn't change – mo7ty May 29 '22 at 18:28
  • @mo7ty Just tested on 20.04 and it works for me. Did you copy and paste correctly? I see in the code you used, you use in place editing which can only be used on a file, so is your data in a file or variable? If file, please remove the redirect – HatLess May 29 '22 at 18:38
  • Another `sed` solution `sed -e "/$category/{:a;/done/i$append" -e 'n;/^$/!ba}' file` – HatLess May 29 '22 at 19:06
  • your two last options indeed print in the console the expected result, but the report file itself is not modified – mo7ty May 29 '22 at 19:37
  • @mo7ty That would be because you have not included the `i` in place flag. `sed -i "/$category/{:a;/done/s/^/$append\n/;n;/^$/!ba}" file`. I assumed you were aware of this as it is in your original code. – HatLess May 29 '22 at 19:39
  • It works! The only update I was doing was to ref the report var `$file`. What if there are empty lines before `done`? – mo7ty May 29 '22 at 19:45
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245152/discussion-between-mo7ty-and-hatless). – mo7ty May 29 '22 at 19:48
1

This might work for you (GNU sed):

sed -e '/fruit:/,/done/{/done/i\orange' -e '}' file

Focus on the fruit category and insert orange before the category delimiter i.e. done.

N.B. The solution is in two parts as the i command expects a newline or another command set.

Alternative:

sed '/fruit:/,/done/!b;/done/i\orange' file
potong
  • 55,640
  • 6
  • 51
  • 83
0

As an alternative, here is a gnu-awk that does the job using custom RS:

awk -v append='\norange' -v category='fruit:' -v RS='\ndone\n' '
{ORS=RT} $1 == category {$0 = $0 append} 1' file

vehicle:
car
moto
done

fruit:
banana
apple
orange
done

tree:
pine
oak
done
EOT

To save change into the same file use:

awk -i inplace -v append='\norange' -v category='fruit:' -v RS='\ndone\n' '{ORS=RT} $1 == category {$0 = $0 append} 1' file
anubhava
  • 761,203
  • 64
  • 569
  • 643