151

Is there a way to get the integer that wc returns in bash?

Basically I want to write the line numbers and word counts to the screen after the file name.

output: filename linecount wordcount Here is what I have so far:

files=\`ls`
for f in $files;
do
        if [ ! -d $f ] #only print out information about files !directories
        then
                # some way of getting the wc integers into shell variables and then printing them
                echo "$f $lines $words"
        fi
done
2
  • 2
    See Why you shouldn't parse the output of ls Commented Jan 30, 2022 at 21:00
  • 2
    ...it would be far more reliable to write for f in *; do and skip $files entirely. If you want to store a list of filenames, the correct data structure is an array: files=( * ); for f in "${files[@]}"; do if [ ! -d "$f" ]; then ... -- note the quotes, they're important; if you run your code through shellcheck.net and follow the links in the warnings it throws, they go to wiki pages explaining why. Commented Jan 30, 2022 at 21:00

19 Answers 19

102

Most simple answer ever:

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

5 Comments

But doesn't produce the desired output.
sure it does, you just pass in the proper parameters that you would anyway. for example, if you do wc -c < file name, it will give you just the integer number of bytes and nothing else.
My point was that if you're going to answer a (1.5 yr-old) question) might as well put all the info into the answer, that's all :)
whoops, just re-read the question.. #fail, i was researching how to get just the integers, and i was using information from one of the answers, when i discovered a better answer, and then decided to post about it. sorry..
The only trickiness to this approach is that if you want to quiesce the error channel, you have to do it before the input, like so: wc -l 2>/dev/null < /path/to/filename. You can't redirect the error channel at the end of the pipeline like this usually: wc -l < /path/to/filename 2>/dev/null. This is a crucial difference in how you use this approach, especially if you use this in a script and might not always have a valid filename.
95

Just:

wc -l < file_name

will do the job. But this output includes prefixed whitespace as wc right-aligns the number.

4 Comments

To get just line count without whitespace: wc -l < foo.txt | xargs ref - stackoverflow.com/a/12973694/149428
So, while the command produces an answer with leading spaces, the questioner was assigning to a variable and that drops the leading spaces automatically (at least that was my experience on the older OSX bash). In other words, lines=`wc -l < $f` results in a variable "line" whose value has no leading spaces.
What if I'm piping with with find? I get an error: find . -path -prune -o -name "*.swift" -print0 | xargs -0 wc -l works ok but prints every file. If I use find . -path -prune -o -name "*.swift" -print0 | xargs -0 wc -l < I get an error. parse error near '\n'
@cycollins, whether spaces are dropped depends on the current value of IFS; it's not reliable behavior.
68

You can use the cut command to get just the first word of wc's output (which is the line or word count):

lines=`wc -l $f | cut -f1 -d' '`
words=`wc -w $f | cut -f1 -d' '`

3 Comments

Running wc twice for the same file is inefficient.
True, that's why I think James Broadhead's answer is much better. I couldn't get cut to work on the full output of wc $f because the number of spaces between fields varies.
@casablanca You can squeeze the number of spaces using 'tr -s'. I have listed more portable solutions to this question in my answer below.
66
+50

Sometimes wc outputs in different formats in different platforms. For example:

In OS X:

$ echo aa | wc -l

         1

In Centos:

$ echo aa | wc -l

1

So using only cut may not retrieve the number. Instead try tr to delete space characters:

$ echo aa | wc -l | tr -d ' '

2 Comments

This answer works cross-platform, is concise, and above all, correctly addresses the full question, formatting included. Thanks a lot!
Does not work on OSX, please check @rouble version.
58
wc $file | awk {'print "$4" "$2" "$1"'}

Adjust as necessary for your layout.

It's also nicer to use positive logic ("is a file") over negative ("not a directory")

[ -f $file ] && wc $file | awk {'print "$4" "$2" "$1"'}

1 Comment

On my Windows in Cmder (bash 4.4.12) this works: wc $file | awk '{print $4" "$2" "$1}' -- apostrophes outside of {}.
38

The accepted/popular answers do not work on OSX.

Any of the following should be portable on bsd and linux.

wc -l < "$f" | tr -d ' '

OR

wc -l "$f" | tr -s ' ' | cut -d ' ' -f 2

OR

wc -l "$f" | awk '{print $1}'

1 Comment

Indeed. This answer really works cross-platform. Testes on OSX, bsd, debian and Ubuntu. Thanks a lot!
15

If you redirect the filename into wc it omits the filename on output.

Bash:

read lines words characters <<< $(wc < filename)

or

read lines words characters <<EOF
$(wc < filename)
EOF

Instead of using for to iterate over the output of ls, do this:

for f in *

which will work if there are filenames that include spaces.

If you can't use globbing, you should pipe into a while read loop:

find ... | while read -r f

or use process substitution

while read -r f
do
    something
done < <(find ...)

6 Comments

I upvoted you anyway, but you don't need to redirect the filename. Just capture it as an additional field. read lines words characters filename <<< $(wc "$f") Oh, and if you used * to be hardened against spaces, you also want to double quote the var when you use it.
Using an additional variable is indeed another way to do it, but if you're not going to use the variable (perhaps you already have the filename in a variable - $f in this case), it's unnecessary. Also, the OP asked for how to get just the integer. You are correct about quoting a variable, but I didn't show the use of the variable, so I omitted mentioning that point. The for f in * form should almost always be used instead of using the output of ls, as you know.
I completely agree on the ls point. That'll get you an award. partmaps.org/era/unix/award.html#ls
If you don't want the filename, you could do this: read lines words characters _ <<< $(wc "$f")
@EvanTeitelman: As discussed in a couple of the comments above yours. $_ is just a different variable.
|
3

If the file is small you can afford calling wc twice, and use something like the following, which avoids piping into an extra process:

lines=$((`wc -l "$f"`))
words=$((`wc -w "$f"`))

The $((...)) is the Arithmetic Expansion of bash. It removes any whitespace from the output of wc in this case.

This solution makes more sense if you need either the linecount or the wordcount.

Comments

2

How about with sed?

wc -l /path/to/file.ext | sed 's/ *\([0-9]* \).*/\1/'

Comments

1
typeset -i a=$(wc -l fileName.dat  | xargs echo | cut -d' ' -f1)

Comments

1

Try this for numeric result:
nlines=$( wc -l < $myfile )

Comments

1

There is a great solution with examples on stackoverflow here

I will copy the simplest solution here:

FOO="bar"
echo -n "$FOO" | wc -l | bc                     # "3"

Maybe these pages should be merged?

Comments

0

Something like this may help:

#!/bin/bash
printf '%-10s %-10s %-10s\n' 'File' 'Lines' 'Words'
for fname in file_name_pattern*; {
    [[ -d $fname ]] && continue
    lines=0
    words=()
    while read -r line; do
        ((lines++))
        words+=($line)
    done < "$fname"
    printf '%-10s %-10s %-10s\n' "$fname" "$lines" "${#words[@]}"
}

Comments

0

To (1) run wc once, and (2) not assign any superfluous variables, use

read lines words <<< $(wc < $f | awk '{ print $1, $2 }')

Full code:

for f in *
do
    if [ ! -d $f ]
    then
        read lines words <<< $(wc < $f | awk '{ print $1, $2 }')
        echo "$f $lines $words"
    fi
done

Example output:

$ find . -maxdepth 1 -type f -exec wc {} \; # without formatting
 1  2 27 ./CNAME
  21  169 1065 ./LICENSE
 33 130 961 ./README.md
  86  215 2997 ./404.html
  71  168 2579 ./index.html
 21  21 478 ./sitemap.xml

$ # the above code
404.html 86 215
CNAME 1 2
index.html 71 168
LICENSE 21 169
README.md 33 130
sitemap.xml 21 21

Comments

0

Solutions proposed in the answered question doesn't work for Darwin kernels.

Please, consider following solutions that work for all UNIX systems:

  1. print exactly the number of lines of a file:
wc -l < file.txt | xargs
  1. print exactly the number of characters of a file:
wc -m < file.txt | xargs
  1. print exactly the number of bytes of a file:
wc -c < file.txt | xargs
  1. print exactly the number of words of a file:
wc -w < file.txt | xargs

1 Comment

This has nothing to do with the kernel -- whether you have a BSD version of wc or a GNU version of wc is completely unrelated to the OS kernel providing the syscalls used by wc to perform I/O operations. If you ran GNU wc on Linux, you'd have the same behavior as GNU wc on Darwin; likewise, if you compiled Apple's wc on Linux, it would behave the same way it does on Darwin.
-1

Try this:

wc `ls` | awk '{ LINE += $1; WC += $2 } END { print "lines: " LINE  " words: " WC }'

It creates a line count, and word count (LINE and WC), and increase them with the values extracted from wc (using $1 for the first column's value and $2 for the second) and finally prints the results.

Comments

-1

"Basically I want to write the line numbers and word counts to the screen after the file name."

answer=(`wc $f`)
echo -e"${answer[3]}
lines:  ${answer[0]}
words:  ${answer[1]}
bytes:  ${answer[2]}"

Outputs : myfile.txt lines: 10 words: 20 bytes: 120

3 Comments

Don't use the -e argument to echo without a very good reason -- POSIX outright disallows it, and consequently, major shells can be configured to turn it off (or don't treat it as anything other than an instruction to print the string -e by default).
Moreover, you don't need -e here at all -- your newlines are literal to start with, so you don't need any transformation from the two-character \n sequence to the single-character newline in the first place. And beyond that, not having any space between the -e and the opening quotes make behavior very much undefined.
(and beyond that, performing an unquoted expansion introduces a bunch of unnecessary variables into this software's reliability -- f/e, if the filename contains spaces, then ${answer[3]} will have only the first part of it; or if the first part of the filename can be expanded as a glob, you can get a list of other filenames in your current directory in its place -- consider the file created with touch '* READ ME FIRST *').
-1
files=`ls`
echo "$files" | wc -l | perl -pe "s#^\s+##"

1 Comment

If you are using perl anyway, might as well do the word count in Perl too. A more economical approach would be to pipe to tr -d '[:space:]'
-1

You have to use input redirection for wc:

number_of_lines=$(wc -l <myfile.txt)

respectively in your context

echo "$f $(wc -l <"$f") $(wc -w <"$f")"

Comments

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.