4

Suppose that I have ten bash shell scripts: script1.sh, script2.sh, ..., script10.sh. Initially, all ten files are identical.

Now, I would like to make two changes in each script:

  1. In each file, I would like to change a particular line (say line 8) -- that is, deleting whatever is on line 8 and replacing it with a "constant" string that I specify, such as "This is line 8." This is similar to this question, but there they wanted to replace "AAA" with "BBB", whereas I would like to replace line 8 (whatever it is) with "This is line 8.".

  2. In each file, I would like to change another particular line (say line 21) and replace it with a "variable" string that I specify. For example, in script1.sh I want to change line 21 to "XYZ"; in script2.sh I want to change line 21 to "PQR"; and in script3.sh I want to change line 21 to "ABC". Essentially this is just many calls to the function in (1) above -- except that I would be making the change in one individual file rather than in all files, and that I am specifying ten different strings rather than just one. So to obtain (2) here, perhaps I would just call (1) ten different times with different parameters.

I am interested in solutions that use commonly available Linux programs like bash, vi, awk, gawk, etc.

Andrew
  • 16,855

3 Answers3

5
for file in f1 f2; do
  sed -i '8s/^.*$/foo/' "$file"
done
Hauke Laging
  • 90,279
2

Using awk

for file in script1.sh script2.sh script3.sh ... script10.sh; do
    temp=$(mktemp)
    awk '
        BEGIN {
            line8 = "This is line 8"
            line21["script1.sh"] = "XYZ"
            line21["script2.sh"] = "PQR"
            line21["script3.sh"] = "ABC"
            # ... 
        }
        FNR == 8 { print line8; next }
        FNR == 21 && FILENAME in line21 { print line21[FILENAME]; next }
        {print}
    '  "$file" > "$temp" && mv "$temp" "$file"
done

or, using bash and sed

# bash variables to store new values. requires bash v4 for the associative array
line8="This is line 8"
declare -A line21=(
    [script1.sh]="XYZ"
    [script2.sh]="PQR"
    [script3.sh]="ABC"
    # ...
)
# then use sed
for file in script1.sh script2.sh script3.sh ... script10.sh; do
    sed -i "8s/.*/$line8/; 21s/.*/${line21[$file]}/" "$file"
done

With the sed solution, you have to be careful that the new lines do not contain "/" characters, as that will break the s/// command. Although you can use different delimiter characters, such as s|pattern|replacement| (in which case the same warning applies, recursively)

ed works too:

for file in script1.sh script2.sh script3.sh ... script10.sh; do
    ed "$file" <<ED_COMMANDS
8d
8i
$line8
.
21d
21i
${line21[$file]}
.
w
q
ED_COMMANDS
done
glenn jackman
  • 85,964
2

I will answer both questions but, as a general rule, please don't combine questions like this, split them into separate posts instead.

Question 1

  1. Perl:

    for f in *.sh; do 
      perl -i -lne '$.==8 ? print "This is line 8" : print;' "$f"; 
    done
    
  2. awk and variants:

    for f in *.sh; do 
      awk '{if(NR==8){print "This is line 8"}else{print}}' "$f" > fifo && 
      mv fifo "$f";
    done
    

Question 2

I would first create a file with the replacements I want to make. For example:

script1.sh  foo1
script2.sh  foo2
script3.sh  foo3
script4.sh  foo4

Then read that file to perform the substitutions:

while read f s; do 
 export s;
 perl -i -lne '$.==8 ? print "$ENV{s}" : print;' "$f";
done < sublist.txt
terdon
  • 242,166