Russ Thils
Russ Thils

Reputation: 150

Writing a Shell Function that accepts input from a pipe and arguments simultaneously

I'm trying to write a function that I call logAction specifically for Ksh that reads input from a pipe and accepts arguments in the same function call. I've been trying to debug this for 2 weeks searching on Google and sites like this.

The purpose of the function that I'm trying to create is to simplify the process of logging output to a log file for other scripts that I've developed. I intend to use the command for a variety of situations, but the 2 easiest to describe and illustrate what I'm attempting are similar to the following:

Syntax Example 1:

logAction 1 "Script executed some action."

Syntax Example 2:

COMMAND1 | COMMAND2 | logAction 1

As you can see, I want to be able to pass the intended log information to the function either as an argument or as stdinput through a pipe in order to capture the output of a command sequence. A 3rd scenario would be to prepend some text before the piped output, i.e.

Syntax Example 3:

COMMAND1 | COMMAND2 | logAction 1 "This is the output:  "

The output below illustrates what the result would be from using the syntax in Example 3:

[2015/12/28 10:20:32] This is the output:  <Output from COMMAND1 | COMMAND 2>

Here's a basic skeleton of the function I'm trying to create.

function logAction {
  while read -t 0 -u 0 stdinput
  do
    echo "StdInput:'$stdinput'"
    pipedinput=$stdinput
  done
  pipedinput=${pipedinput:-}

  case $1 in
    1)  #Output text only to log file
        output=$(echo "$pretag ${@:2} $pipedinput")
        echo $output >> $LOG
    2)  #Output text to screen and log file
        output=$(echo "$pretag ${@:2} $pipedinput")
        echo $output
        echo $output >> $LOG
    3)  
        ....
  esac
  unset pipedinput
}

Originally, I didn't supply the -t argument to the read command, and the function would pause execution until the user entered CTRL-D to indicate EOF. However, for my purposes, this function is intended to have silent operation and should not request user input. Adding the -t 0 argument with the read command tells the read command to timeout with 0 seconds instead of waiting for EOF from the keyboard.

This solved the problem for some cases; however, the read command doesn't capture any input when used with the cat command like so:

cat somefile.txt | logAction 1
cat somefile.txt | awk '{ a=a" "$1 }END{ print a }' | logAction 1

Yet using echo seems to work:

echo "ab bc cd de ef fg gh" | logAction 1 "This is the output: "

I have been able to make it work with cat by changing the 0 for the timeout to 0.1 (i.e. read -t 0.1), but I'm concerned that this is a dirty solution. This seems to indicate there is a race condition where the read command is being executed before the pipe has finished its output and closed. This wasn't what I expected, as I thought the preceding command in a pipe sequence must finish execution before the subsequent command is executed.

Is there a way to tell the read command to ignore stdin from the keyboard and use the stdout from the preceeding command in the pipe? I tried to tell it to use FD0 (which I thought was the stdout from the pipe, but it seems to also be connected to the keyboard) with the the -u 0 argument, but that didn't seem to work.

Upvotes: 1

Views: 3483

Answers (1)

Ziffusion
Ziffusion

Reputation: 8943

Try this:

#!/bin/ksh

function log 
{
    ARG1=$1
    ARG2=$2

    if [ ! -t 0 ] 
    then
        INPUT=$(cat)
    else
        INPUT=""
    fi  

    echo "<datetime>" ${ARG2}${INPUT}
}

log 1 "Script executed some action."

echo "<data from pipe>" | log 2 "this is the output: "

echo "<data from pipe>" | cat | log 2 "this is the output: "

Output:

<datetime> Script executed some action.
<datetime> this is the output: <data from pipe>
<datetime> this is the output: <data from pipe>

Upvotes: 5

Related Questions