lucasarcotti
lucasarcotti

Reputation: 13

bash script input from pipe and wc -m

I have this code:

#!/bin/bash
# ~/test.sh
if [[ -p /dev/stdin ]]; then
var="-"
else
var="$1"
fi
l=$(cat -n "$var" | wc -l)
c=$(cat -n "$var" | wc -m)
echo l=$l
echo c=$c

If i run:

$ ./test.sh file.txt
l=73
c=4909 # it is correct

but if

cat file.txt | ./test.sh
l=73
c=0    #why i have 0???

i don't understand why i have c=0

cat file.txt | cat -n - | wc -c works; so what's the problem with my code?

what am I doing wrong?

how can i make it work properly?

Upvotes: 1

Views: 610

Answers (2)

Léa Gris
Léa Gris

Reputation: 19615

When you pipe rather than open a file, the difference is that the piped-in or stdin is a stream (no seek back to start).

l=$(cat -n "$var" | wc -l)

Consumes the whole stream to count lines.

When it reaches:

c=$(cat -n "$var" | wc -m)

The stdin stream has all been consumed by the previous counting of lines. It has nothing to count left, thus returning 0.

Fortunately wc can do both on the same run, and then you can read the result values like this:

#!/usr/bin/env bash

var=${1--}
if [ -p /dev/stdin ]; then
  if [ $# -ge 1 ]; then
    printf %s\\n 'Need either stdin or filename argument (not both)!' >&2
    exit 1
  fi
elif [ $# -ne 1 ]; then
  printf %s\\n 'Need stdin or a single filename argument!' >&2
  exit 1
fi
read -r l c _ < <(wc -lm -- "$var")
printf 'l=%d\nc=%d\n' "$l" "$c"

Upvotes: 2

tripleee
tripleee

Reputation: 189628

The first cat consumes standard input until end of file; there is then no way to read the same data again for the second cat.

The usual solution to this is to use a temporary file; but if you just want the different counts from wc, run it once and extract them both from its output.

set -- $(cat -n "${@--}" | wc -l -m)
l=$1
c=$2

Notice also how the parameter expansion "${@--}" expands to either "$@" or - depending on whether the script received arguments.

Upvotes: 1

Related Questions