143

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
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Jordan
  • 4,928
  • 4
  • 26
  • 39
  • 2
    See [Why you shouldn't parse the output of `ls`](https://mywiki.wooledge.org/ParsingLs) – Charles Duffy Jan 30 '22 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 http://shellcheck.net/ and follow the links in the warnings it throws, they go to wiki pages explaining why. – Charles Duffy Jan 30 '22 at 21:00

19 Answers19

99

Most simple answer ever:

wc < filename 
BananaNeil
  • 10,322
  • 7
  • 46
  • 66
  • 6
    But doesn't produce the desired output. – Dave Newton Jan 29 '12 at 13:51
  • 5
    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. – BananaNeil Jan 30 '12 at 21:07
  • 14
    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 :) – Dave Newton Jan 30 '12 at 23:47
  • 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.. – BananaNeil Jan 31 '12 at 05:22
  • 6
    You've answered Google, that's what counts. :-) – Ciro Santilli OurBigBook.com Nov 19 '15 at 23:06
  • 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. – ikaerom Jan 02 '23 at 09:24
93

Just:

wc -l < file_name

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

anishpatel
  • 1,472
  • 1
  • 16
  • 23
sten
  • 7,028
  • 9
  • 41
  • 63
  • 3
    To get just line count without whitespace: `wc -l < foo.txt | xargs` ref - https://stackoverflow.com/a/12973694/149428 – Taylor D. Edmiston Feb 07 '19 at 22:17
  • 1
    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. – cycollins Jun 11 '19 at 20:47
  • 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'` – Nuno Gonçalves Sep 29 '19 at 20:32
  • @cycollins, whether spaces are dropped depends on the current value of `IFS`; it's not reliable behavior. – Charles Duffy Jan 30 '22 at 21:03
66

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' '`
casablanca
  • 69,683
  • 7
  • 133
  • 150
  • 5
    Running wc twice for the same file is inefficient. – gawi Sep 19 '10 at 18:42
  • 3
    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 Sep 19 '10 at 18:49
  • 1
    @casablanca You can squeeze the number of spaces using 'tr -s'. I have listed more portable solutions to this question in my answer below. – rouble Feb 09 '16 at 05:19
56

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 ' '
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Tahsin Turkoz
  • 4,356
  • 1
  • 27
  • 18
55
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"'}
Jolta
  • 2,620
  • 1
  • 29
  • 42
James Broadhead
  • 1,878
  • 1
  • 16
  • 19
34

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}'
tripleee
  • 175,061
  • 34
  • 275
  • 318
rouble
  • 16,364
  • 16
  • 107
  • 102
14

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 ...)
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 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. – Bruno Bronosky Mar 14 '13 at 17:54
  • 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. – Dennis Williamson Mar 14 '13 at 23:34
  • I completely agree on the `ls` point. That'll get you an award. http://partmaps.org/era/unix/award.html#ls – Bruno Bronosky Mar 18 '13 at 17:48
  • If you don't want the filename, you could do this: `read lines words characters _ <<< $(wc "$f")` –  May 16 '13 at 20:52
  • @EvanTeitelman: As discussed in a couple of the comments above yours. `$_` is just a different variable. – Dennis Williamson May 17 '13 at 00:10
  • @DennisWilliamson: `$_` is often used as a sort of sink variable since its contents are overwritten by bash after every command. Using it avoids having to create a new variable. –  May 17 '13 at 13:14
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.

tripleee
  • 175,061
  • 34
  • 275
  • 318
Walter Tross
  • 12,237
  • 2
  • 40
  • 64
2

How about with sed?

wc -l /path/to/file.ext | sed 's/ *\([0-9]* \).*/\1/'
mgold
  • 6,189
  • 4
  • 39
  • 41
1
typeset -i a=$(wc -l fileName.dat  | xargs echo | cut -d' ' -f1)
Sgoettschkes
  • 13,141
  • 5
  • 60
  • 78
1

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

paulonki
  • 17
  • 2
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[@]}"
}
Ivan
  • 6,188
  • 1
  • 16
  • 23
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
CH.
  • 556
  • 1
  • 5
  • 16
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
shogitai
  • 1,823
  • 1
  • 23
  • 50
  • 1
    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. – Charles Duffy Jan 30 '22 at 21:01
0

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?

John
  • 469
  • 1
  • 4
  • 17
-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.

Aif
  • 11,015
  • 1
  • 30
  • 44
-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

  • Don't use the `-e` argument to `echo` without a _very_ good reason -- [POSIX outright disallows it](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html), 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). – Charles Duffy Jan 30 '22 at 21:04
  • 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. – Charles Duffy Jan 30 '22 at 21:06
  • (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 *'`). – Charles Duffy Jan 30 '22 at 21:08
-1
files=`ls`
echo "$files" | wc -l | perl -pe "s#^\s+##"
Pedro Reis
  • 1,587
  • 1
  • 19
  • 19
  • 1
    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:]'` – tripleee Feb 19 '19 at 06:12
-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")"
user1934428
  • 19,864
  • 7
  • 42
  • 87