Davide A.
Davide A.

Reputation: 3

Return the read cursor to the start of the file

I'm trying to read into two file (name,number) at the same time and get value of each possible pair. The two file are like this:

*name1   
John            
*name2                      
Paul
*number1        
25        
*number2         
45

What i'm trying to obtain are label and result like:

*name1 *number1 John 25
*name2 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45

Since i come from python i've tried to do it with two loop like this:

name=/home/davide/name.txt
number=/home/davide/number.txt
while read name; do
    if [[ ${name:0:1} == "*" ]]; then
        n=$(echo $name)
    else
        while read number; do
                if [[ ${number:0:1} == "*" ]]; then
                    echo $number $n
                else
                    echo $name $number 
                fi
        done < $number
    fi
done < $name 

I have the first two pair so my guess it's that i need a command to start from the beginning of number again (like seek(0) on python) but i haven't found a similar one for bash. I also get an "ambiguous redirect" error and i don't understand why.

Upvotes: 0

Views: 610

Answers (5)

Hkoof
Hkoof

Reputation: 796

In your script, you use the variable name for both the file path and the while-loop variable. This causes the "ambiguous redirect" error. Two lines need fix e.g.:

name_file=/home/davide/name.txt
done < $name_file

No need to for seek(0) in shell scripts. Just process the file again, e.g:

while read line ; do
    echo "== $line =="
done < /some/file

while read line ; do
    echo "--> ${line:0:1}"
done < /some/file

This is less efficient and less flexible than a more real programming language where you can seek(). But that's about differences, advantages and disadvantages between shell scripting and programming.

By the way, this line:

n=$(echo $name)

... is merely a awkward way of just doing:

n=$name

This can cause your script to behave quite unpredictable when $name contains special character like *. And since $name is read from a text file, this not unlikely to happen. (thanks Charles Duffy for making this point)

Upvotes: 0

Ed Morton
Ed Morton

Reputation: 203995

$ cat tst.awk
NR==FNR {
    if ( NR%2 ) {
        tags[++numPairs] = $0
    }
    else {
        vals[numPairs] = $0
    }
    next
}
!(NR%2) {
    for (pairNr=1; pairNr<=numPairs; pairNr++) {
        print prev, tags[pairNr], $0, vals[pairNr]
    }
}
{ prev = $0 }

$ awk -f tst.awk number.txt name.txt
*name1 *number1 John 25
*name1 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295639

After setting up your input files:

printf >name.txt '%s\n' '*name1' John '*name2' Paul                      
printf >number.txt '%s\n' '*number1' 25 '*number2' 45

...the following code:

#!/usr/bin/env bash
name_file=name.txt
number_file=number.txt

while IFS= read -r name1 && IFS= read -r value1; do
  while IFS= read -r name2 && IFS= read -r value2; do
    printf '%s\n' "$name1 $name2 $value1 $value2"
  done <"$number_file"
done <"$name_file"

...properly outputs:

*name1 *number1 John 25
*name1 *number2 John 45
*name2 *number1 Paul 25
*name2 *number2 Paul 45

What changed?

  • We stopped using name and number both for the filenames and for the values read from them. Because of this, when you ran <$number, it no longer had the filename number.txt in it after the first iteration; likewise for $name.
  • We started quoting all expansions ("$foo", not $foo). See the http://shellcheck.net/ warning SC2086, and BashPitfalls #14, explaining why even echo $foo is buggy.
  • Running read with the -r argument and IFS set to an empty value prevents it from consuming literal backslashes or pruning leading and trailing newlines.
  • Using two reads inside the condition of each while loop lets us read two lines at a time from each file (as is appropriate, given the intent to process content in pairs).

Upvotes: 1

Wiimm
Wiimm

Reputation: 3572

First, in your example you overwrite the variable $number. So you have issues on reading file $number beginning from the second loop-run.

Solution with paste

Command paste can combine multiple files, and with option -d line-by-line.

#!/usr/bin/env bash
name=/home/davide/name.txt
number=/home/davide/number.txt

# combine both files linb-by-line
paste $'-d\n' "$name" "$number" |
while read nam
do
    #after reading name to var 'nam', read number to var 'num':
    read num

    # print both
    echo "$nam $num"
done

if you want TABS or any other separator and no other processing, you don't need the while loop. Examples

paste "$name" "$number"
paste -d: "$name" "$number"
paste -d\| "$name" "$number"

Upvotes: 0

KamilCuk
KamilCuk

Reputation: 141493

Bash operates more easly on "streams", not like, on the data itself.

  • first substitute every second newline starting from the first for a tabulation or a space or other separator
  • then "paste" the files together
  • Then rearange columns, from *name1 John *number1 25 to *name1 *number1 John 25

cat >name.txt <<EOF
*name1
John
*name2
Paul
EOF

cat <<EOF >number.txt
*number1
25
*number2
45
EOF

paste <(<name.txt sed 'N;s/\n/\t/') <(<number.txt sed 'N;s/\n/\t/') |
awk '{print $1,$3,$2,$4}'

will output:

*name1 *number1 John 25
*name2 *number2 Paul 45

Upvotes: 0

Related Questions