user1070725
user1070725

Reputation: 89

How to perform arithmetic operations on the output of a shell command

I'm trying to count the number of entries in a set of log files. Some of these logs have lines that should not be counted (the number of these remains constant). The way I'd like to go about this is a Perl script that iterates over a hash, which maps log names to a one-liner that gets the number of entries for that particular log (I figured this would be easier to maintain than dozens of if-else statements)

Getting the number of lines is simple:

wc -l [logfile] | cut -f1 -d " "

The issue is when I need to subtract, say, 1 or 2 from this value. I tried the following:

expr( wc -l [logfile] | cut -f1 -d " " ) - 1

But this results in an error:

Badly placed ()'s.

: Command not found.

How do I perform arithmetic operations on the output of a shell command? Is there a better way to do this?

Upvotes: 0

Views: 1912

Answers (3)

fork2execve
fork2execve

Reputation: 1650

The existing answers went in the direction of solving your issue in perl, which you mentioned but your own experiments were in shell syntax.

You indicated tcsh but expr is Posix shell syntax.

Here is an example of a csh script that counts the number of lines in a file whose name it is passed and then does arithmetic on the number of lines.

set lines=`wc -l < $1`
@ oneless = ($lines - 1)
echo "There are $lines in $1 and minus one makes $oneless"

Test:

csh count.csh count.csh
There are 3 lines in count.csh and minus one makes 2

Upvotes: 0

Borodin
Borodin

Reputation: 126742

It's a bit clunky to shell out to wc and cut just to count the number of lines in a file.

Your requirement isn't very clear, but this Perl code creates a hash that relates every log file in the current directory to the number of lines it contains. It works by reading each file into an array of lines, and then evaluating that array in scalar context to give the line count. I hope it's obvious how to subtract a constant delta from each line count.

use strict;
use warnings;

my %lines;

for my $logfile ( glob '*.log' ) {

    my $num_lines = do {
        open my $fh, '<', $logfile or die qq{Unable to open "$logfile" for input: $!};
        my @lines = <$fh>;
    };

    $lines{$logfile} = $num_lines;
}

Update

After a comment from w.k, I think this version may be rather nicer

use strict;
use warnings;

my %lines;

for my $logfile ( glob '*.log' ) {
    open my $fh, '<', $logfile or die qq{Unable to open "$logfile" for input: $!};
    1 while <$fh>;
    $lines{$logfile} = $.;
}

Upvotes: 0

John1024
John1024

Reputation: 113934

To display one less than the number of lines with bash or any bourne-like shell:

echo $(( $(wc -l <file) - 1 ))

Discussion

To get the number of lines, you used:

wc -l logfile | cut -f1 -d " " 

cut is required here because wc copies the file name to its output. To avoid that, and thus avoid the need for cut, supply the input to wc via stdin:

wc -l <logfile

In modern (POSIX) shells, arithmetic is done with $((...)). Thus, we can substract one from the number of lines via:

$(( $(wc -l <file) - 1 ))

Upvotes: 1

Related Questions