2

For example, the only thing in file.txt looks like this:

xxxxxxxxxHAHAxxxxxxHOHOxxxxxxx  

I hope to replace HAHA with seq 1 3 and replace HOHO with seq 5 7, so the output should be:

xxxxxxxxx1xxxxxx5xxxxxxx   
xxxxxxxxx2xxxxxx6xxxxxxx   
xxxxxxxxx3xxxxxx7xxxxxxx   

What I did:

for i in $(seq 1 3)    
do  sed "s/HAHA/$i/g" file.txt   
  for i in $(seq 5 7)    
  do sed "s/HOHO/$i/g" file.txt   
  done   
done > new.txt     

But new.txt doesn't show what I expected. How should I change the code?

  • By the way, I added four spaces to each line of my code, but it doesn't format like what it should be like – XYZ Mar 4 at 1:58
2

Here's one way you could do it, using the bash built-in read command's ability to read from different file descriptors:

while read -u3 i && read -u4 j; do 
    sed -e "s/HAHA/$i/" -e "s/HOHO/$j/" file.txt
done > new.txt 3< <(seq 1 3) 4< <(seq 5 7)

Since your numbers have a simple arithmetic relationship, it would be simpler to use a single seq process + some shell arithmetic:

for i in $(seq 1 3); do
  sed -e "s/HAHA/$i/" -e "s/HOHO/$((i+4))/" file.txt
done > new.txt

In either case, see Why is using a shell loop to process text considered bad practice? - the inefficiencies may not matter for the minimal example you have provided, but if you are doing anything more serious you should consider using a different approach.

  • 1
    I wonder why you use 3 and 4 after the -u. Did you just choose these numbers randomly? – XYZ Mar 4 at 2:27
  • 1
    @LittleG they are the first two numbers after the terminal's standard input / output / error streams (fd 0, 1, 2) that are available as file descriptors – steeldriver Mar 4 at 2:32
  • 1
    In the first solution, why do you add a space between the two <? – XYZ Mar 4 at 2:46
  • 1
    @LittleG iirc it's necessary in order for the shell parser to distinguish < <(command) from the here-document syntax << word, but I may be wrong about that: see for example What are the shell's control and redirection operators? – steeldriver Mar 4 at 2:58
  • 1
    @LittleG you may not be able to avoid a loop, but you can avoid a shell loop, as shown in Kusalananda's answer – steeldriver Mar 5 at 0:08
2

Without calling an external utility for each line produced:

read template <file

paste <(seq 1 3) <(seq 5 7) |
while read x y; do
    t=${template/HAHA/$x}
    t=${t/HOHO/$y}
    printf '%s\n' "$t"
done >new.txt

This first reads the line from the file called file into $template. It then constructs a two-column input for a while read loop. The input to the loop is the two columns of numbers from seq.

In the loop, some bash-specific substitutions are performed on the value of $template to replace the HAHA and HOHO string with the numbers read from paste. This creates $t which is then outputted.


Using only awk, and assuming that the input is a single line only:

awk '{
         for (i = 1; i <= 3; ++i) {
             t = $0;
             sub("HAHA",   i, t)
             sub("HOHO", 4+i, t)
             print t
         }
     }' file

A shell loop for bash, mimicking the awk code above:

read template <file

for (( i = 1; i <= 3; ++i )); do
    t=${template/HAHA/$i}
    t=${t/HOHO/$((i+4))}
    printf '%s\n' "$t"
done
  • You said 'Without calling an external utility' - does this mean it runs faster than calling sed? – XYZ Mar 4 at 21:11
  • 1
    @LittleG Calling an external utility, like sed, in a loop is slow in comparison to using only built-in utilities in a loop, yes. – Kusalananda Mar 4 at 21:45

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.