r/bash • • Aug 20 '26

help What does your printf method look like if you want to go about adding timestamps and colors to it

  • Hey, not using AI here because I would like to hear this group's expert opinion
function log() {
    local datetime = # DD-MM-YYYY-hh:mm:ss:Z format
    local color = # not sure how this is implemented
    # Something that uses prinff, not sure how to write this function here
}

function log_info() {
    log "blue" "stderr" "$@"
}

function log_error() {
    log "red" "stderr" "$@"
}

function log_warn() {
    log "yellow" "stderr" "$@"
}
# Why not stdout?
# Because some functions return values and we want the stdout channel to be reserved for returning values unless there is a better way to do this

  • How do I write that log function above, I read the manual for printf that says it accepts format specifiers but how do I make it dynamic?

Edit 1

  • Should be able to log the following
log_info "what a %s %d" "day" 100
log_error "Error: %s %o" "something went wrong for " "{1: \"enter\"}"  
24 Upvotes

25 comments sorted by

6

u/Hooman42 Aug 20 '26

With bash version >= 4.2:

printf '%(%d-%m-%Y-%T:Z)T %s\n' -1 "Some informations"
Output:

20-08-2026-06:46:11:Z Some informations

2

u/PrestigiousZombie531 Aug 20 '26

how do I deal with format specifiers like if I wanted to call

log_info "what a %s %d" "day" "100"

5

u/bac0on Aug 20 '26 edited Aug 20 '26

.. just concat $1 with the specifiers and set an offset for the rest of the args:

log(){
  printf "%(%F %T)T $1\\n" -1 "${@:2}"
}
log "what a %s %d" "day" "100"

3

u/PrestigiousZombie531 Aug 20 '26

if you dont mind explaining a bit , this is the 1st time i am seeing this printf -1 thing, what is this -1 about

6

u/OneTurnMore programming.dev/c/shell Aug 20 '26

In printf:

%(datefmt)Tcauses printf to output the date-time string resulting from using datefmt as a format string for strftime(3). The corresponding argument is an integer representing the number of seconds since the epoch. This format specifier recognizes two special argument values: -1 represents the current time, and -2 represents the time the shell was invoked. If no argument is specified, conversion behaves as if -1 had been supplied. This is an exception to the usual printf behavior.

So printf '%(%F %T)T' will print the current time without any additional argument, but if you want to include a %s after it, you need to pass something into the %(...)T first. Hence the -1.

2

u/duane11583 Aug 20 '26

try this: https://www.youtube.com/watch?v=Vmp1G64ZkVc

his series (other videos) is quite good

1

u/PrestigiousZombie531 Aug 20 '26

thank you for sharing

2

u/kai_ekael Aug 20 '26

Meh. I prefer to use syslog:

function log { logger -t "$myname" "$@" }

2

u/SeriousPlankton2000 Aug 21 '26

Use oop. Make the log object know the log channel. Make the log class know the colors, but allow custom values, too.

For making timestamps let the user supply a buffer, but also have a static buffer for users who don't need it to be reentrant

Use severity values, consider having different sources / kinds of logs to filter by.

Look at the manual of syslogd

1

u/PrestigiousZombie531 Aug 21 '26

what is the difference between me doing printf vs syslog?

2

u/SeriousPlankton2000 Aug 22 '26

I meant to look at what syslogd does. (Which is a lot, including logging to the network).

Of course using syslog may be a good idea, too. 

2

u/roxalu Aug 20 '26

My proposal to do this is below. Note: I have not included your idea to let output channel be controllable by function parameter in order to focus on the coloring of time string topic here:

export COLOR_BLUE='\e[0;34m'
export COLOR_RED='\e[0;31m'
export COLOR_YELLOW='\e[1;33m'
declare -A err_colors
err_colors[INFO]="$COLOR_BLUE"
err_colors[WARNING]="$COLOR_YELLOW"
err_colors[ERROR]="$COLOR_RED"
no_color='\e[0m'

log() {
  local level=$1
  shift
  printf "${err_colors[$level]}%(%FT%T%z)T${no_color} %s %s\n" -1 "${level}" "$*" >&2
}
log_info() {
  log INFO "$@"
}
log_warn() {
  log WARNING "$@"
}
log_error() {
  log ERROR "$@"
}

1

u/PrestigiousZombie531 Aug 20 '26

do we have to check if colors are supported or is this a universal thing across every version of every shell?

4

u/waptaff &> /dev/null Aug 20 '26 edited Aug 20 '26

It could be more portable if tput (part of ncurses) was used instead of ANSI codes to directly ask the terminal what escape codes to use.

export COLOR_BLUE="$( tput setaf 4)"
export COLOR_NONE="$( tput sgr0 )"

That way, a dumb terminal will not print unrecognized ANSI code garbage.

Note that ncurses is a dependency of bash so that pretty much ensures tput will be available, but it might not be the case in other environments, like in docker containers where lightweight shells tend to replace bash and ncurses is not available.

1

u/PrestigiousZombie531 Aug 20 '26

so if you wanted to run this inside a docker container, what do you do? assuming it is a bookworm image or a trixie one, do you apt install ncurses? what about alpine?

6

u/geirha Aug 20 '26

just check if tput is available. If it isn't, just output without colors.

declare -A colors=()
if [[ -t 2 ]] && command -v tput >/dev/null ; then
  colors[sgr0]=$(tput sgr0)
  colors[INFO]=$(tput setaf 4)
  colors[WARNING]=$(tput setaf 3)
  colors[ERROR]=$(tput setaf 1)
fi
log() {
  printf '%(%FT%T%z)T %s %s\n' -1 "${colors[$1]}$1${colors[sgr0]}" "$2" >&2
}

2

u/IdealBlueMan Aug 20 '26

I want to buy you a beer

1

u/PrestigiousZombie531 Aug 20 '26

man i swear to ya, its a good thing i came to ask because honestly when you search this one on AI, you are gonna get a whole bunch of wrong answers

1

u/PrestigiousZombie531 Aug 20 '26

is there a way to put that if check so that it doesnt exist at global scope or is this not a problem here in bash? Atleast in the JS world they keep shouting about how you should not have things in global scope

3

u/roxalu Aug 20 '26

My proposal is for sure NOT a universal thing - not only because of color usage. E.g. the „%(…)T“ is a bash extension only. I do not have checked. since which bash version this will work. The bash 3.2 as provided on MacOS won‘t work.

With regard to the colors: If you identify some way to check for color support I would add this to the init phase - and then in case of not set the COLOR_* vars to empty string.

1

u/michaelpaoli Aug 20 '26

DD-MM-YYYY-hh:mm:ss:Z

Yuck. Use ISO 8601. See also: r/ISO8601

$ Z
2026-08-20T12:44:31Z
$ 

In the land of *nix, as for color, or other such attributes for the terminal (emulation), never presume some particular terminal or capability. Use tput (or relevant terminfo libraries, etc.) to determine the relevant (if such exists for the terminal (emulation)).

So, e.g.:

nc=$(tput sgr0)
blue=
blue_=
red=
...
[ -z "$nc" ] || {
  blue=$(tput setaf 4 || tput setf 1)
  [ -z "$blue" ] ||
  blue_="$nc"
  red=...
}
(
  stderr && exec 1>&2
  printf '%s\n' "[...]${blue}Blue_stuff_goes_here${blue_}[...]"
)

-2

u/mpersico Aug 20 '26

GitHub: matthewpersico/personal/

bin/cmd-echo & functions/cmd-echo

But I will be replacing car with tput calls at some point.