25

I want to create a bash alias for grep that adds line numbers:

alias grep='grep -n'

But that, of course, adds line numbers to pipelines as well. Most of the time (and no exceptions come to mind) I don't want line numbers within a pipeline (at least internally, probably OK if it's last), and I don't really want to add a sed/awk/cut to the pipeline just to take them out.

Perhaps my requirements could be simplified to "only add line numbers if grep is the only command on the line." Is there any way to do this without a particularly ugly alias?

Kevin
  • 40,767

2 Answers2

28

You could use a function in bash (or any POSIX shell) like this:

grep() { 
    if [ -t 1 ] && [ -t 0 ]; then 
        command grep -n "$@"
    else 
        command grep "$@"
    fi
}

The [ -t 1 ] part uses the [ command (also known as test) to check if stdout is associated with a tty.

The [ -t 0 ] checks standard input, as well, since you specified to only add line numbers if grep is the only command in the pipeline.

Wildcard
  • 36,499
enzotib
  • 51,661
3

(for completeness)

While @enzotib's answer is most probably what you want, it's not what you asked for. [ -t 1 ] checks if the file descriptor is a terminal device, not that it's anything other than a pipe (like a regular file, a socket, an other type of device like /dev/null...)

The [ command has no equivalent of -t but for pipes. To get the type of the file associated with a file descriptor, you need to perform the fstat() system call on it. There's no standard command to do that, but some systems or shells have some.

With GNU stat:

grep() {
  if { [ "$(LC_ALL=C stat -c %F - <&3)" = fifo ]; } 3>&1 ||
     [ "$(LC_ALL=C stat -c %F -)" = fifo ]; then
    command grep "$@"
  else
    command grep -n "$@"
  fi
}

Or with zsh and its own stat builtin (which predates GNU's one by a few years), here loaded as zstat only:

grep() {
  zmodload -F zsh/stat b:zstat
  local stdin_type stdout_type
  if zstat -A stdin_type -s -f 0 +mode &&
     zstat -A stdout_type -s -f 1 +mode &&
     [[ $stdin_type = p* || $stdout_type = p* ]]
  then
     command grep "$@"
  else
     command grep -n "$@"
  fi
}

Now a few notes:

It's not only shell pipelines that use pipes.

var=$(grep foo bar)

or:

cmd <(grep foo bar)

or:

coproc grep foo bar

also run grep with its stdout going to a pipe.

If your shell is ksh93, note that on some systems, it uses socketpairs instead of pipes in its pipelines.