0

I can iterate over the lines of a file this way:

while read l; do echo $l; done < file

Is there a way to iterate over only the top 5 lines?

  • head -5 file | while read l; do echo $l; done – Artem S. Tashkinov May 31 '22 at 16:34
  • pipe through head -5, but see https://unix.stackexchange.com/q/9954/170373 – ilkkachu May 31 '22 at 16:35
  • And yet another option: n=5; i=0; while read l; do echo $l; i=$(($i+1)); if [ $i -ge $n ]; then break; fi; done < file – Jim L. May 31 '22 at 17:07
  • @JimL. (though you need IFS= read -r l to keep the data intact, see https://unix.stackexchange.com/q/169716/170373 and even then you may get problems if the file is missing the trailing newline, see https://unix.stackexchange.com/questions/478720/what-does-while-read-r-line-n-line-mean) – ilkkachu May 31 '22 at 17:43
  • @ikkachu Thanks. I was merely reciting the O.P.'s definition of "iterate". – Jim L. May 31 '22 at 17:49

3 Answers3

4

Just do something like:

n=5
while IFS= read -ru3 line && (( n-- )); do
  printf 'Got this line: "%s"\n' "$line"
done 3< some-file

Though, here, if it's about text processing, best would probably be to use a text processing tool:

LC_ALL=C sed 's/.*/Got this line: "&"/;5q' < some-file

Or:

awk '{print "Got this line: \""$0"\""}; NR == 5 {exit}' < some-file

Or:

perl -lne 'print qq(Got this line: "$_"); last if $. == 5' < some-file

Related:

2

There are many ways to iterate over the first five lines of a file; here are a few. Note that the last ones are the most efficient and are generally a better way to approach this kind of problem than using a shell script loop.

Brute force:

{
    OIFS="$IFS" IFS=
    read -r line && printf "%s\n" "$line"
    read -r line && printf "%s\n" "$line"
    read -r line && printf "%s\n" "$line"
    read -r line && printf "%s\n" "$line"
    read -r line && printf "%s\n" "$line"
    IFS="$OIFS" 
} <file

A loop:

for ((i=1; i<=5; i++))
do
    IFS= read -r line
    printf "%s\n" "$line"
done <file

Another loop, suitable for small ranges as the expression is expanded before evaluation into a list of all its values (i.e. {1..5} is converted to 1 2 3 4 5 before execution):

for i in {1..5}
do
    IFS= read -r line
    printf "%s\n" "$line"
done <file

Considering just the beginning of the file, but beware that any variables set in this loop will not be accessible outside of it

head -n5 file |
    while IFS= read -r line
    do
        printf "%s\n" "$line"
    done

Not using a loop at all

head -n5 file

sed 5q file

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • 1
    Is there a reason not to use for i in {1..5} instead of for ((i=1; i<=5; i++)). Just to learn, that's why I'm asking. – schrodingerscatcuriosity May 31 '22 at 17:12
  • 2
    It's another variation. Yours expands as for i in 1 2 3 4 5 and wouldn't necessarily be scalable to (say) {1..1000000}, whereas mine represents i=1; while (( i <= 5 )) do ... ((i++)); done and should scale to any range. – Chris Davies May 31 '22 at 17:37
  • 1
    @schrodingerscatcuriosity The brace expansion would have to be computed and expanded in full before the loop could start. This becomes an issue if you want to run many iterations (256 MB for a million numbers; the list of numbers must be stored in memory). With an arithmetic for loop, you only store the loop variable in memory. Traditional for loops (the first kind) always iterates over a static list stored in memory. See also bash counting script, counts fine ascending, doing error by counting descending – Kusalananda May 31 '22 at 17:39
1

You can use process substitution, to redirect from the output of head -n 5 file rather than just file. Unlike using a pipe from head -n 5 file, with process substitution, the while loop runs in the current shell and is able to set/change variables in that shell and otherwise affect its environment - a child process or subshell, e.g. a pipe, is not able to affect its parent's environment.

For example:

while read l; do printf '%s\n' "$l"; done < <(head -n 5 file)

I'd include an explanation of why I'm using printf instead of echo and a warning about not using shell to process text, but Stéphane's answer has already done that. I recommend that you read the links in that answer.

cas
  • 78,579