I've created a shell script but I don't know how to do a loop that prints the number of lines in every file you pass to the script as argument. So that the output would be something like this:

2 lines in hello.sh
33 lines in /var/log/syslog

Assuming a POSIX shell, you want a for loop here, looping over "$@" which contains the command line arguments:

for filename in "$@"; do
    lines=$(wc -l < "$filename")
    printf '%u lines in %s\n' "$lines" "$filename"

Using all the quotes is quite important. It allows you to pass filenames that contain spaces and other characters.

./my_script.sh /var/log/syslog "$HOME/My Dir with Spaces/a file with spaces.txt"
  • 1
    Note that "$@" is the default target for for, so for filename; do is sufficient. May 15 '18 at 7:27

In general, to loop over the command line argument in a script or shell function, you may do

for thing do
    commands using "$thing"


for thing in "$@"; do
    commands using "$thing"

In this case, assuming filenames given on the command line do not contain literal newlines, there's no need to do an explicit shell loop for this:

wc -l -- "$@" | sed -E '$d;s/^[[:blank:]]*([[:digit:]]*)[[:blank:]]*(.*)/\1 lines in \2/'

This would take all the command line arguments and run wc -l on them all.

The output is parsed by sed which discards the last line containing the total number of lines in the given files (using $d). The rest of the output from wc -l is transformed into the output that you want by capturing the number of lines and the filename and then inserting the string lines in in-between.

Running the script with my own .vimrc, .profile and .mailrc files will return

8 lines in .vimrc
8 lines in .profile
4 lines in .mailrc

Notice that wc -l -- "$@" will create output like

   8 .vimrc
   8 .profile
   4 .mailrc
  20 total

which may actually be enough for what you want, without the need for sed.

  • Note that the amount of space in between the number of lines and the file name varies with the wc implementation (POSIX only guarantees there will be at least one blank). May 15 '18 at 6:55
  • @StéphaneChazelas Thanks for all the editing! This means that one can not reliably capture initial blanks in the filename in the output from wc -l unless one knows exactly how many blanks it uses as the separator. I've changed it so that it eats up all blanks, assuming the output will only be used for presentation.
    – Kusalananda
    May 15 '18 at 6:59

for file in "${@}"; do
    wc -l -- "${file}"

#!/bin/bash Program that will execute this script.

${@} Positional arguments. Arguments that you give to the script through command line.

file Variable that will iterate over the arguments given in ${@}.

wc -l -- "${file}" External command that will print the number of lines in ${file} (except when ${file} is - in which case it will print the number of lines on its standard input).


Alternative using while and shift

while [ $# -gt 0 ] ; do
    printf '%u lines in %s\n' $(wc -l < "$1") "$1"

Not the answer you're looking for? Browse other questions tagged or ask your own question.