Get just the integer from wc in bash
Asked Answered
O

19

146

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
Older answered 19/9, 2010 at 18:33 Comment(2)
See Why you shouldn't parse the output of lsGoggleeyed
...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.Goggleeyed
P
67

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' '`
Pict answered 19/9, 2010 at 18:40 Comment(3)
Running wc twice for the same file is inefficient.Marilee
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.Pict
@Pict You can squeeze the number of spaces using 'tr -s'. I have listed more portable solutions to this question in my answer below.Filippa
O
100

Most simple answer ever:

wc < filename 
Optometry answered 29/1, 2012 at 13:31 Comment(5)
But doesn't produce the desired output.Elidaelidad
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.Optometry
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 :)Elidaelidad
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..Optometry
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.Iconoclast
D
94

Just:

wc -l < file_name

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

Disorientate answered 8/4, 2013 at 14:47 Comment(4)
To get just line count without whitespace: wc -l < foo.txt | xargs ref - https://mcmap.net/q/11871/-how-to-trim-whitespace-from-a-bash-variableIbanez
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.Gesticulatory
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'Retroaction
@cycollins, whether spaces are dropped depends on the current value of IFS; it's not reliable behavior.Goggleeyed
P
67

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' '`
Pict answered 19/9, 2010 at 18:40 Comment(3)
Running wc twice for the same file is inefficient.Marilee
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.Pict
@Pict You can squeeze the number of spaces using 'tr -s'. I have listed more portable solutions to this question in my answer below.Filippa
I
59

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 ' '
Imam answered 26/10, 2015 at 21:55 Comment(2)
This answer works cross-platform, is concise, and above all, correctly addresses the full question, formatting included. Thanks a lot!Stipulate
Does not work on OSX, please check @Filippa version.Inhabit
R
57
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"'}
Rodd answered 19/9, 2010 at 18:40 Comment(1)
On my Windows in Cmder (bash 4.4.12) this works: wc $file | awk '{print $4" "$2" "$1}' -- apostrophes outside of {}.Quarterhour
F
36

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}'
Filippa answered 28/1, 2016 at 5:41 Comment(1)
Indeed. This answer really works cross-platform. Testes on OSX, bsd, debian and Ubuntu. Thanks a lot!Inhabit
H
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 ...)
Hypocrite answered 19/9, 2010 at 22:27 Comment(6)
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.Cumming
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.Hypocrite
I completely agree on the ls point. That'll get you an award. partmaps.org/era/unix/award.html#lsCumming
If you don't want the filename, you could do this: read lines words characters _ <<< $(wc "$f")Kalinin
@EvanTeitelman: As discussed in a couple of the comments above yours. $_ is just a different variable.Hypocrite
@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.Kalinin
P
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.

Peugia answered 25/6, 2015 at 14:18 Comment(0)
B
2

How about with sed?

wc -l /path/to/file.ext | sed 's/ *\([0-9]* \).*/\1/'
Buffum answered 28/12, 2012 at 3:44 Comment(0)
N
1
typeset -i a=$(wc -l fileName.dat  | xargs echo | cut -d' ' -f1)
Naos answered 4/7, 2012 at 13:17 Comment(0)
A
1

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

Attired answered 28/6, 2013 at 14:20 Comment(0)
C
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[@]}"
}
Corolla answered 17/12, 2020 at 19:6 Comment(0)
B
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
Bigotry answered 17/12, 2020 at 19:24 Comment(0)
H
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
Hippocrates answered 26/7, 2021 at 9:47 Comment(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.Goggleeyed
S
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?

Swart answered 30/1, 2022 at 20:56 Comment(0)
A
-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.

Archivist answered 19/9, 2010 at 18:43 Comment(0)
N
-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

Nieves answered 13/6, 2017 at 14:25 Comment(3)
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).Goggleeyed
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.Goggleeyed
(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 *').Goggleeyed
C
-1
files=`ls`
echo "$files" | wc -l | perl -pe "s#^\s+##"
Chablis answered 13/11, 2017 at 14:25 Comment(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:]'Grenville
M
-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")"
Meryl answered 16/12, 2020 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.