0

I've written a general purpose logger function in bash called trace() whose 3rd argument and beyond supposed to club and print as text, preserving any embedded newlines. But it doesn't. I'm looking for an effect similar to echo command that does it as intended (tried below). Looks like the club-together thing is to blame (${@:3})?

timeStamp() { echo `date "+%Y-%m-%d %H:%M:%S:%3N %Z"` ;}

trace() {
  lineNum=$1
  traceType=$2
  traceText=${@:3} #Input Parameters 3rd and beyond
  #echo -en "[${lineNum}][$(timeStamp)][${traceType}]: ${traceText}"
  #printf "[%s][%s][%s]: %s\n" ${lineNum} "$(timeStamp)" ${traceType} "${traceText}"
  printf "[${lineNum}][$(timeStamp)][${traceType}]: ${traceText}"
}

Trials/Output:

$ trace $LINENO ERR "This is
a multiline
text
that's supposed to go
upto 5th line"
[342][2017-08-04 00:42:02:062 EDT][ERR]: This is a multiline text that's supposed to go upto 5th line

$ echo "This is a
> multiline text
> that's supposed
> to go
> upto 5th line"
This is a
multiline text
that's supposed
to go
upto 5th line
Dan
  • 10,303
  • 5
  • 36
  • 53
MaxGrand
  • 113
  • 1
  • 9

2 Answers2

3

The problem is indeed in traceText=${@:3}. Normally, $@ is equivalent to $1 $2 $3... (and ${@:3} to $3 $4 $5...), and since it isn't in double-quotes, each of those goes through word splitting and wildcard expansion. But in the case of var=$@ it can't assign multiple values to var, so the word splitting and wildcard expansion gets suppressed... but apparently not completely. It's apparently doing enough word splitting to convert newlines to spaces.

I'm not clear if this is a bug or not; IMO it's a situation that doesn't really make sense because of the conflict between $@ (treat each parameter as a separate item) and = (which can only assign one value). IMO what you should be using is traceText="${*:3}", which is unambiguous -- the double-quotes explicitly suppress word splitting and wildcard expansion, and the $* means take all the arguments stuck together with spaces (or whatever the first character of IFS is).

In my testing (with bash v3.2.57), all of these work as expected:

traceText=${*:3}
traceText="${*:3}"
traceText="${@:3}"

The only one that gives weird results is with @ and without double-quotes.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
0

In addition to the comment, you can also rewrite trace() altogether to include a command substitution for date and simply use the positional parameters in the printf format string. For example:

trace() { 
    printf "[$1]$(date "+%Y-%m-%d %H:%M:%S:%3N %Z")][$2]: ${@:3}"
}

Example Use/Output

$ trace 123 "some error" "this
is my
multi-line
text
"
[123]2017-08-04 01:01:29:765 CDT][some error]: this
is my
multi-line
text

note: there is a limit on the number of positional parameters that can be passed, so if your multi-line text exceeds the limit, trace will not work. See: Bash command line and input limit

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • That won't work right if there are more than 3 arguments, because arguments past `$3` will be treated as separate arguments to `printf`, and unlike `echo` it doesn't just mash its arguments together (it does something considerably more complicated). Switching to `${*:3}` (which mashes all the arguments together before passing them to `printf`) would work. – Gordon Davisson Aug 04 '17 at 06:10
  • Thanks Gordon, that is a good point. It is a rather screwy way to handle reproducing multi-line text. You would think the preference would be to pass it on `stdin` and `while read -r ...` the input. – David C. Rankin Aug 04 '17 at 06:13
  • Thanks both @GordonDavisson & DavidCRankin. I'll try the unambiguous ${*:3} option and keep you posted. – MaxGrand Aug 04 '17 at 06:42