Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add built in printf function #614

Closed
ekimekim opened this issue Nov 10, 2014 · 18 comments
Closed

Add built in printf function #614

ekimekim opened this issue Nov 10, 2014 · 18 comments

Comments

@ekimekim
Copy link

As it stands, there is currently little flexibility in how to convert values to a string.
What I would like is a printf-style function that can format a string based on inputs. I'd imagine something like:
printf(format_str) : Takes a list as input and outputs a string. Each element of the list is used as the input to the respective format character. The list must be the correct length.

For example:
["foo", 3.14159, 2.71828] | printf("Hello %5s, your data is %.2f, %d")
would produce
"Hello foo, your data is 3.14, 2"

Another possible option that would work better with the existing string interpolation might be a function that simply does the tostring conversion with a format spec, like 1|format("02d") == "01".
Then my above example might look more like:
["foo", 3.14159, 2.71828] | "Hello \(.[0]|format("5s")), your data is \(.[1]|format(".2f")), \(.[2]|format("d"))"

@wtlangford
Copy link
Contributor

I definitely think that, given those two choices, a more typical printf() is the correct route. The string interpolation solves a simpler problem than printf does and I think it is best kept that way.

The question becomes how complete do we want this printf to be? I'm going to strongly advise against passing through to sprintf and friends, as I see a security/safety problem there which merits an amount of consideration. The other side of that, though, is that using sprintf and friends means we can support (essentially) full printf specifiers, assuming we can enforce some kind of type safety.

That being said, I'm all for this. I just think it should be thought through a bit more. @nicowilliams, @pkoppstein, your thoughts?

@pkoppstein
Copy link
Contributor

@wtlangford wrote:

I just think it should be thought through a bit more.

Based in part on @nicowilliams emphasis elsewhere on the virtues of composition, I had assumed that the Tao of Jq would lead to a filter along the lines of the proposed format, at least for floats, which is where the current gap in functionality is most pronounced. This way of doing things is also in line with the way filters are used in web frameworks (see e.g. https://docs.djangoproject.com/en/dev/ref/contrib/humanize/) and meta-frameworks (if AngularJS be such).

Perhaps it's not really an either-or situation, just a question of which comes first?

@nicowilliams
Copy link
Contributor

A printf would be nice, yes, mostly so as to handle alignment and number formatting options.

@vlcinsky
Copy link

My need is to be able converting integers into hex representation (that is how I came to this issue).

Looking forward to see printf in next jq version..

@dolzenko
Copy link

Sorry to hijack, but that issue comes up as the "convert to string result", so I decided not to create a new one. Soo how does one currently convert number to string?

@pkoppstein
Copy link
Contributor

@dolzenko: tostring

@pbkdf3
Copy link

pbkdf3 commented Aug 14, 2016

anyone ending up here might want to look at string interpolation, does 99% of what I'd use printf for: https://stedolan.github.io/jq/manual/#Stringinterpolation-\(foo)

@Wolf480pl
Copy link

@pbkdf3 look at the example in the first post / issue description - it's mostly about various number conversion/formatting options (decimal/hex/scientific, leading zeros/spaces, decimal places, etc.)

@nicowilliams nicowilliams removed this from the 1.6 release milestone Feb 26, 2017
@nicowilliams
Copy link
Contributor

A proper printf will have to wait until we have varargs support, whenever that might be. In the meantime you can use string interpolation.

@tomwhoiscontrary
Copy link

I would like to convert timestamps expressed in milliseconds since the epoch to ISO 8601 strings, with the milliseconds part. jq's built-in time formatting doesn't support milliseconds, so i could use printf, or some more sophisticated string interpolation, to format the milliseconds part, but jq doesn't have that either. One or the other would be really useful!

For my own future reference, i ended up with this:

(.event.timestamp / 1e3 | strftime("%Y-%m-%dT%H:%M:%S")) + ".\(.event.timestamp / 100 % 10)\(.event.timestamp / 10 % 10)\((.event.timestamp) % 10)Z"

@mmoya
Copy link

mmoya commented Oct 3, 2019

Linking to #1341 for further reference.

@ygoe
Copy link

ygoe commented Nov 28, 2019

I'd already be happy to pad a string with spaces to a specified length. Can't find a function for that and printf should be able to do that.

@johnhawkinson
Copy link

@nicowilliams wrote four years ago:

A proper printf will have to wait until we have varargs support, whenever that might be. In the meantime you can use string interpolation.

The things I want to use printf for don't seem to be possible with string interpolation, (nor do they strictly require varargs support).
For instance, I want to right-align 3-digit numbers (minimum working example):

$ echo '[94, 172]' | jq -r '.[]' | awk '{$1=sprintf("%3d", $1); print}'
 94
172

Or various other printf conversions. Is there some workaround short of filtering through an external tool like awk?
If not, should this issue be reopened, or is there a more appropriate issue for this?

@pkoppstein
Copy link
Contributor

Padding is easily done using defs such as these:

def lpad($len; $fill): tostring | ($len - length) as $l | ($fill * $l)[:$l] + .;

def lpad($len): lpad($len; " ");

def lpad: lpad(4);

Certainly having a printf would be convenient, but in terms of string manipulation, jq is already quite capable, thanks to its support for regular expressions via Oniguruma.

@lgfausak
Copy link

since the string is interpolated when parsed we can't save the 'format' for later use:

$ jq -rn '"test\(.num)" as $fs|{"num":1}|$fs'
testnull

this one works...

$ jq -rn '{"num":1}|"test\(.num)"'
test1

i tried like this, no luck:

$ jq -rn '"test\\(.num)" as $fs|{"num":1}|$fs'
test\(.num)

an eval like function would be needed to make interpolation work with a saved 'format' string.

@pkoppstein
Copy link
Contributor

@lgfausak wrote:

we can't save the 'format' for later use

One can "save" a format using a suitable def.

For example:

def f($num): "number: ($num)";

@wader
Copy link
Member

wader commented Jan 11, 2022

@lgfausak if you looking for some kind of simpler templating with dynamic strings then i've used something like this:

def template($s):
  ( . as $env
  | $s
  | gsub("\\{(?<var>.*?)}"; $env[.var])
  );

{name: "jq"} | template("hello {name}")

@robzr
Copy link

robzr commented Apr 5, 2024

I made a module that implements printf in jq - hope this is of use to someone - https://github.com/robzr/jq-printf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests