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?

sbmthakur
  • 1
  • 1
  • `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:

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
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
roaima
  • 107,089
  • 14
  • 139
  • 261
  • 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. – roaima 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](https://unix.stackexchange.com/q/587729) – 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
  • 1
  • 7
  • 119
  • 185