When preparing datasets or tabular text files in Unix/Linux environments, a common task is prepending a unique value or sequence number to the beginning of each line. However, combining Bash loops with text-processing tools like awk often leads to unexpected Cartesian products (each value applied to every line) rather than line-by-line pairing.

The Problem: Why Nested Bash Loops Duplicate Lines

Consider the original code attempt:

for b in 0.1 0.15; do cat Edata | awk '{print '$b', $0;}'; done

In this loop, Bash runs the entire cat Edata | awk ... pipeline once for 0.1 (printing every line in the file), and then runs it completely again for 0.15. Instead of updating line 1 with 0.1 and line 2 with 0.15, the whole file is processed repeatedly.

Here are several cleaner, faster, and more idiomatic ways to map one list of values to corresponding lines in a file.

Solution 1: Use paste (The Simplest Approach)

If you have an array or list of values to merge line-by-line with an existing file, the standard Unix utility paste is the most efficient choice.

paste -d ' ' <(printf "%s\n" 0.1 0.15) Edata

How it works:

  • <(printf "%s\n" 0.1 0.15) creates a temporary stream containing each number on a new line.
  • paste -d ' ' merges the two streams side-by-side using a single space as the delimiter.

Solution 2: Pure awk with an Array

If you prefer an all-in-one awk command, you can pass your values directly into awk as an array and match each element to the current line number (FNR or NR):

awk 'BEGIN { split("0.1 0.15", nums) } { print nums[NR], $0 }' Edata

How it works:

  • The BEGIN block runs before processing the file and splits the string of numbers into the nums array.
  • For each line read from Edata, NR represents the current line number. nums[NR] fetches the corresponding value and prints it before $0 (the original line).

Solution 3: Processing Two Separate Files with awk

If your prefixes are stored in another file (e.g., numbers.txt), you can combine both files using awk without loading entire datasets into memory:

awk 'NR==FNR { a[NR]=$0; next } { print a[FNR], $0 }' numbers.txt Edata

How it works:

  • NR==FNR evaluates to true only while reading the first file (numbers.txt), storing each line in array a.
  • next skips to the next line without executing the rest of the script.
  • Once reading Edata begins, FNR resets to 1, and print a[FNR], $0 outputs the matching line from numbers.txt followed by the line from Edata.

Solution 4: Read Line-by-Line with a Bash while Loop

If you are already inside a Bash script and have an array of prefixes, you can step through the file line-by-line:

prefixes=(0.1 0.15)
i=0
while IFS= read -r line; do
    printf "%s %s\n" "${prefixes[i]}" "$line"
    ((i++))
done < Edata

Summary

Avoid executing awk inside a for loop over an entire file. For quick command-line operations, paste offers the shortest and fastest syntax. If more complex transformation logic is needed, use awk with split() or multi-file processing using NR==FNR.