How do I select specific rows with sed?

Viewed 255

In manual of sed, the page show the methods to select specific lines but in a range.
How can I select spcific lines which have no rules?

seq 10

For example, how can I change line 1, 3, 7 to 11?
I tried use seq 10 | sed '1s/[0-9]/10/;3s/[0-9]/10/;7s/[0-9]/10/', but this looks complex and unclear.
Is there a way to do this easier?

5 Answers

This might work for you (GNU sed):

printf '%ds/[0-9]/10/\n' {1,3,{7..11}} | sed -f - file

Convert the numeric sequence (by way of bash brace expansion) into a series of sed commands and pass them as stdin to the file option to a sed invocation using file as the input file.

Or as the OP might wish:

printf '%ds/[0-9]/10/\n' {1,3,{7..11}} | sed -f - <(seq 10)

Another way to write the same solution:

seq 10 | sed $(printf '%ds/[0-9]/10/;' {1,3,{7..11}})

Yet another way:

<<<"1,3,7 to 11" sed -E 's/$/,/;s/,/ba;/g;s/\s*(to|-|\.\.)\s*([0-9]+)/,\2ba;/g;s/;ba//g' |
sed -f - -e 'b;:a;s/[0-9]/10/' <(seq 10)

This could be efficiently done in awk program. Create an awk variable named lines and give all the line numbers there with comma separated form which you want to change. You could also give a range eg: 7-11 your case, which I have used in my program too.

Pass your Input_file to this program and this will substitute digit with 10 as per your shown attempts but this will only print output on terminal. Once you are Happy with results then append > temp && mv temp Input_file to following code, which will perform inplace save into Input_file.

awk -v lines="1,3,7-11" '
BEGIN{
  num=split(lines,arr,",")
  for(i=1;i<=num;i++){
     if(arr[i]~/-/){
        split(arr[i],arr2,"-")
        start=arr2[1]
        while(start<=arr2[2]){
           subArr[start++]
        }
     }
     else{ subArr[arr[i]] }
  }
}
(FNR in subArr){
   sub(/[0-9]/,"10")
}
1' Input_file

If your aim is clarity, does the code below meet your requirements?

seq 10 | awk '(NR == 1) || (NR == 3) || ((NR >= 7) && (NR <= 11)){gsub(/[0-9]/,"10")} {print}'

Just use awk:

$ seq 10 | awk '
    BEGIN {
        split("1 3 7",tmp)
        for (i in tmp) {
            tgts[tmp[i]]
        }
    }
    NR in tgts { sub(/[0-9]/, 10) }
    { print }
'
10
2
10
4
5
6
10
8
9
10

or if you wanted to include the ability to specify a range as the other answers do:

$ seq 10 | awk '
    BEGIN {
        split("1 3 7-11", tmp)
        for (i in tmp) {
            n = split(tmp[i], range, /-/)
            for (j=range[1]; j <= range[n]; j++) {
                tgts[j]
            }
        }
    }
    NR in tgts { sub(/[0-9]/, 10) }
    { print }
'
10
2
10
4
5
6
10
10
10
100

If you are willing to use perl one-liner:

seq 10 | perl -lane 's/[0-9]/10/ if (grep {$_ == $.} (1,3,7..11));print'
10
2
10
4
5
6
10
10
10
100

sed is not suitable when condition gets complicated.
The sed solution Cyrus provided in the comment and potong's answer are both very good.

Related