Combining shell variables and negation in sed

Specific example here is that I want to insert a string inside a second (last) instance of another string, while ignoring another string, e.g. for the input example below, I want to replace every last instance of group_ or field_ on each line, but skip any that contain _type.

The problem is negation is accomplished with:

sed '/pattern to ignore/!s/pattern to replace/replace with/'

This works fine with single quotes, but need double quotes to use shell variables inside sed. With double quotes, the shell complains about the exclamation mark:

bash: !s/\: event not found

One workaround is to place the negation pattern itself inside a shell variable and pass that:

1
2
neg='/pattern to ignore/!'
sed "${neg}s/pattern to replace/replace with/"

Example input:

$ egrep "group|field" test
1
2
3
4
5
6
    'group_type' => 'standard',
    'group_name' => 'group_name',
    'group_name' => 'group_home',
    'group_name' => 'group_family_members',
    'field_name' => 'field_firstname',
    'field_name' => 'field_home_address_1',

Code:

1
2
3
4
5
6
7
neg='/_type/!'
for n in 1 2 3 4; do
  egrep "group|field" test > test.$n
  for t in group field; do
    sed -i "${neg}s/\(.*\)${t}_/\1${t}_l${n}_/" test.$n
  done
done

Result:

1
$ head test.*
==> test.1 <==
    'group_type' => 'standard',
    'group_name' => 'group_l1_name',
    'group_name' => 'group_l1_home',
    'group_name' => 'group_l1_family_members',
    'field_name' => 'field_l1_firstname',
    'field_name' => 'field_l1_home_address_1',
 
==> test.2 <==
    'group_type' => 'standard',
    'group_name' => 'group_l2_name',
    'group_name' => 'group_l2_home',
    'group_name' => 'group_l2_family_members',
    'field_name' => 'field_l2_firstname',
    'field_name' => 'field_l2_home_address_1',
 
==> test.3 <==
    'group_type' => 'standard',
    'group_name' => 'group_l3_name',
    'group_name' => 'group_l3_home',
    'group_name' => 'group_l3_family_members',
    'field_name' => 'field_l3_firstname',
    'field_name' => 'field_l3_home_address_1',
 
==> test.4 <==
    'group_type' => 'standard',
    'group_name' => 'group_l4_name',
    'group_name' => 'group_l4_home',
    'group_name' => 'group_l4_family_members',
    'field_name' => 'field_l4_firstname',
    'field_name' => 'field_l4_home_address_1',

2 Comments

  • 1. kev replies at 5th February 2012, 5:27 am :

    Your first example has a typo:

    sed '/pattern to ignore/!/s/pattern to replace/replace with/'
    -------------------------^-----------------------------------
  • 2. Alain Kelder replies at 5th February 2012, 5:47 pm :

    Fixed. Thanks, kev!

Leave a comment

NOTE: Enclose quotes in <blockquote></blockquote>. Enclose code in <pre lang="LANG"></pre> (where LANG is one of these).