49

Say you have a simple loop

while read line
do
  printf "${line#*//}\n"
done < text.txt

Is there an elegant way of printing the current iteration with the output? Something like

0 The
1 quick
2 brown
3 fox

I am hoping to avoid setting a variable and incrementing it on each loop.

0

5 Answers 5

75

To do this, you would need to increment a counter on each iteration (like you are trying to avoid).

count=0
while read -r line; do
   printf '%d %s\n' "$count" "${line*//}"
   (( count++ ))
done < test.txt

EDIT: After some more thought, you can do it without a counter if you have bash version 4 or higher:

mapfile -t arr < test.txt
for i in "${!arr[@]}"; do
   printf '%d %s' "$i" "${arr[i]}"
done

The mapfile builtin reads the entire contents of the file into the array. You can then iterate over the indices of the array, which will be the line numbers and access that element.

Sign up to request clarification or add additional context in comments.

1 Comment

Nice solution, though mapfile can be awfully memory hungry if you're dealing with large files. Without knowing the OP's file size, I'd recommend going with something that runs through a pipe, and so is filesize agnostic.
33

You don't often see it, but you can have multiple commands in the condition clause of a while loop. The following still requires an explicit counter variable, but the arrangement may be more suitable or appealing for some uses.

while ((i++)); read -r line
do
    echo "$i $line"
done < inputfile

The while condition is satisfied by whatever the last command returns (read in this case).

Some people prefer to include the do on the same line. This is what that would look like:

while ((i++)); read -r line; do
    echo "$i $line"
done < inputfile

1 Comment

@jordanm: for does something similar: for ((i=0, j=14; i<=20; i++, j+=11)). Also, you can read two (or more) lines from a file at a time: while read -r oddline; read -r evenline; do echo "odd: [$oddline] even: [$evenline]"; done < filename
9

You can use a range to go through, it can be an array, a string, a input line or a list.

In this example, i use a list of numbers [0..10] is used with an increment of 2, as well.

#!/bin/bash
for i in {0..10..2}; do 
   echo " $i times"
done

The output is:

 0 times
 2 times
 4 times
 6 times
 8 times
 10 times

To print the index regardless of the loop range, you have to use a variable "COUNTER=0" and increase it in each iteration "COUNTER+1".

my solution prints each iteration, the FOR traverses an inputline and increments by one each iteration, also shows each of words in the inputline:

#!/bin/bash 

COUNTER=0
line="this is a sample input line"

for word in $line; do        
    echo "This i a word number $COUNTER: $word"
    COUNTER=$((COUNTER+1))
done

The output is:

This i a word number 0: this
This i a word number 1: is
This i a word number 2: a
This i a word number 3: sample
This i a word number 4: input
This i a word number 5: line

to see more about loops: enter link description here

to test your scripts: enter link description here

1 Comment

While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value.
5
n=0
cat test.txt | while read line; do
  printf "%7s %s\n" "$n" "${line#*//}"
  n=$((n+1))
done

This will work in Bourne shell as well, of course.

If you really want to avoid incrementing a variable, you can pipe the output through grep or awk:

cat test.txt | while read line; do
  printf " %s\n" "${line#*//}"
done | grep -n .

or

awk '{sub(/.*\/\//, ""); print NR,$0}' test.txt

4 Comments

Edited to include an option for your final criteria.
I prefer the awk solution, though sub(/.*\/\//, ""); is not the same as ${line#*//}
Well ... the first part of your answer is pretty much identical to jordanm's, and the two additional ones seem to work just fine. Chalk it up to the randomness of StackOverflow. :-)
The cat is useless and read without -r will mangle backslashes in the input. This is a really inefficient way to reimplement nl.
1

Update: Other answers posted here are better, especially those of @Graham and @DennisWilliamson.

Something very like this should suit:

tr -s ' ' '\n' <test.txt | nl -ba

You can add a -v0 flag to the nl command if you want indexing from 0.

2 Comments

I don't have an n1 command in FreeBSD, OSX or Illumos. Where is it from?
Ah - pesky monospace fonts. @thb, I think you'd still need something to strip lines up to the //, as ${line#*//} does. But you could certainly pipe the while loop through nl, just as I did with grep -n . in my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.