5

I need to add a character in the n'th position (in this case 2nd) of a matching string. For example, in a file text.txt I would like to add an N before the string and after the " only in the strings that contain blah

text.txt:

"1blah8","na","8blah4"  
"2blah5","na","10blah4"  
"5blah5","na","1blah234"  

I want to get a text2.txt:

"Ν1blah8","na","Ν8blah4"  
"Ν2blah5","na","Ν10blah4"  
"Ν5blah5","na","Ν1blah234"  

I have tried sed 's/.*blah.*/N&/' text.txt > text2.txt but I get the N before the " and only in the first found string of each line.

6

Another approach:

$ sed 's/"\([^"]*blah[^"]*"\)/"N\1/g' test.txt 
"N1blah8","na","N8blah4"  
"N2blah5","na","N10blah4"  
"N5blah5","na","1blah234

The regex is looking for a ", then 0 or more non-" characters followed by a blah, and then then 0 or more non-" again. Because of the parentheses, this is captured and can later be referred to as \1. Therefore, the command will substitute the matched pattern with itself (\1) but with a "N appended. That's why the first " is outside the parentheses. The /g modifier at the end makes it subsstitute all matching string in each line.

If your sed version supports it, you can simplify it to:

sed -E 's/"([^"]*blah[^"]*")/"N\1/g'
5

To get sed to repeat the substitution for all matches on a line, add the g flag after the last /:

$ sed 's/[^"]*blah/N&/g' test.txt

The g flag will cause the substitution to be made "for all non-overlapping matches of the regular expression, not just the first one" (quote from the manual on my system).

I've also changed the regular expression a tiny bit so that all the characters that are not " before blah are matched. This way the substitution will insert the N just before the first digit, just after the ".

2

If you wanted to insert after the nth character (where n is an arbitrary value) then you should avoid sed. There are better tools for this job (like awk or perl, python etc) e.g. with awk you can use a variable n to insert STRING after the nth character in each field that matches PATTERN:

awk -vn=2 'BEGIN{FS=OFS=","}{for (i=1; i<=NF; i++){ if ($i ~ /PATTERN/){
h=substr($i,1,n);t=substr($i,n+1,length($i));$i=h "STRING" t}}};1' infile

This assumes your file is a simple csv file (no commas embedded in your fields).

1

CSV parsing and processing may be real complex. Here is a perl oneliner which put the N in front of column one and three, independently of the content:

Step 1, strip the double quotes:

@F = map { /"(.*)"/ } @F;

Step 2, inserting the N on column one and three

$F[0,2] =~ s/^/N/;

Step 3, join the array as CSV

printf qq("%s"\n), join q(","), @F

run perl:

perl -F, -ane '@F = map { /"(.*)"/ } @F; $F[0,2] =~ s/^/N/; printf qq("%s"\n), join q(","), @F' csvfile

Edit: new approach to avoid the security risk of eval expression (thank you terdon).

  • OP doesn't want to do it "independently of the content"... and the actual data might be different (e.g. blah might be in the 5th column on the 10th row) – don_crissti Feb 5 '17 at 13:53
  • @don_crissti: Do you think, there is a constant "blah" in every record? – ingopingo Feb 5 '17 at 13:55
  • I do not think there's a blah in every record I'm just trying to tell you that you're hardcoding values when there's no point doing that. Unless OP is extremely explicit always assume unknown input. IOW try to answer the Q as it is quite clear: it asks how to insert only when field matches a pattern it doesn't ask to insert in front of column one and two – don_crissti Feb 5 '17 at 13:57
  • @terdon: it works, see the testrun. – ingopingo Feb 5 '17 at 14:24

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.