Skip to content

Commit 97b696f

Browse files
committed
feat: allow writing to output file (close #2418)
This allows to write the output to a file and introduces the options -o and --output-file that take a filename as parameter. When not specifying -o, stdout will be used for compatibility. This will be helpful when calling jq inside a docker context as it means jq will not have to be called from within a shell with output redirection.
1 parent 3c5ceac commit 97b696f

File tree

4 files changed

+42
-13
lines changed

4 files changed

+42
-13
lines changed

docs/content/manual/v1.8/manual.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ sections:
227227
like awk's -f option. This changes the filter argument to be
228228
interpreted as a filename, instead of the source of a program.
229229
230+
* `-o` / `--output-file filename`:
231+
232+
Write output to the file called filename instead of stdout.
233+
230234
* `-L directory` / `--library-path directory`:
231235
232236
Prepend `directory` to the search list for modules. If this

src/jv_print.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ static const char *colors[] = DEFAULT_COLORS;
3535
#define COLORS_LEN (sizeof(colors) / sizeof(colors[0]))
3636
#define FIELD_COLOR (colors[7])
3737

38+
extern FILE *ofile;
39+
3840
static char *colors_buf = NULL;
3941
int jq_set_colors(const char *code_str) {
4042
if (code_str == NULL)
@@ -390,7 +392,7 @@ void jv_dumpf(jv x, FILE *f, int flags) {
390392
}
391393

392394
void jv_dump(jv x, int flags) {
393-
jv_dumpf(x, stdout, flags);
395+
jv_dumpf(x, ofile, flags);
394396
}
395397

396398
/* This one is nice for use in debuggers */

src/main.c

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ extern void jv_tsd_dtoa_ctx_init();
4242

4343
int jq_testsuite(jv lib_dirs, int verbose, int argc, char* argv[]);
4444

45+
FILE *ofile;
46+
4547
/*
4648
* For a longer help message we could use a better option parsing
4749
* strategy, one that lets stack options.
@@ -81,6 +83,7 @@ static void usage(int code, int keep_it_short) {
8183
" each output;\n"
8284
" -a, --ascii-output output strings by only ASCII characters\n"
8385
" using escape sequences;\n"
86+
" -o, --output-file output to file instead of stdout\n"
8487
" -S, --sort-keys sort keys of each object on output;\n"
8588
" -C, --color-output colorize JSON output;\n"
8689
" -M, --monochrome-output disable colored output;\n"
@@ -179,15 +182,15 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts, int options)
179182
while (jv_is_valid(result = jq_next(jq))) {
180183
if ((options & RAW_OUTPUT) && jv_get_kind(result) == JV_KIND_STRING) {
181184
if (options & ASCII_OUTPUT) {
182-
jv_dumpf(jv_copy(result), stdout, JV_PRINT_ASCII);
185+
jv_dumpf(jv_copy(result), ofile, JV_PRINT_ASCII);
183186
} else if ((options & RAW_OUTPUT0) && strlen(jv_string_value(result)) != (unsigned long)jv_string_length_bytes(jv_copy(result))) {
184187
jv_free(result);
185188
result = jv_invalid_with_msg(jv_string(
186189
"Cannot dump a string containing NUL with --raw-output0 option"));
187190
break;
188191
} else {
189192
priv_fwrite(jv_string_value(result), jv_string_length_bytes(jv_copy(result)),
190-
stdout, dumpopts & JV_PRINT_ISATTY);
193+
ofile, dumpopts & JV_PRINT_ISATTY);
191194
}
192195
ret = JQ_OK;
193196
jv_free(result);
@@ -197,15 +200,15 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts, int options)
197200
else
198201
ret = JQ_OK;
199202
if (options & SEQ)
200-
priv_fwrite("\036", 1, stdout, dumpopts & JV_PRINT_ISATTY);
203+
priv_fwrite("\036", 1, ofile, dumpopts & JV_PRINT_ISATTY);
201204
jv_dump(result, dumpopts);
202205
}
203206
if (!(options & RAW_NO_LF))
204-
priv_fwrite("\n", 1, stdout, dumpopts & JV_PRINT_ISATTY);
207+
priv_fwrite("\n", 1, ofile, dumpopts & JV_PRINT_ISATTY);
205208
if (options & RAW_OUTPUT0)
206-
priv_fwrite("\0", 1, stdout, dumpopts & JV_PRINT_ISATTY);
209+
priv_fwrite("\0", 1, ofile, dumpopts & JV_PRINT_ISATTY);
207210
if (options & UNBUFFERED_OUTPUT)
208-
fflush(stdout);
211+
fflush(ofile);
209212
}
210213
if (jq_halted(jq)) {
211214
// jq program invoked `halt` or `halt_error`
@@ -288,6 +291,7 @@ int umain(int argc, char* argv[]) {
288291
#else /*}*/
289292
int main(int argc, char* argv[]) {
290293
#endif
294+
ofile = stdout;
291295
jq_state *jq = NULL;
292296
jq_util_input_state *input_state = NULL;
293297
int ret = JQ_OK_NO_OUTPUT;
@@ -318,9 +322,9 @@ int main(int argc, char* argv[]) {
318322

319323
#ifdef WIN32
320324
jv_tsd_dtoa_ctx_init();
321-
fflush(stdout);
325+
fflush(ofile);
322326
fflush(stderr);
323-
_setmode(fileno(stdout), _O_TEXT | _O_U8TEXT);
327+
_setmode(fileno(ofile), _O_TEXT | _O_U8TEXT);
324328
_setmode(fileno(stderr), _O_TEXT | _O_U8TEXT);
325329
#endif
326330

@@ -419,10 +423,10 @@ int main(int argc, char* argv[]) {
419423
}
420424
} else if (isoption(&text, 'b', "binary", is_short)) {
421425
#ifdef WIN32
422-
fflush(stdout);
426+
fflush(ofile);
423427
fflush(stderr);
424428
_setmode(fileno(stdin), _O_BINARY);
425-
_setmode(fileno(stdout), _O_BINARY);
429+
_setmode(fileno(ofile), _O_BINARY);
426430
_setmode(fileno(stderr), _O_BINARY);
427431
#endif
428432
} else if (isoption(&text, 0, "tab", is_short)) {
@@ -480,6 +484,21 @@ int main(int argc, char* argv[]) {
480484
program_arguments = jv_object_set(program_arguments, jv_string(argv[i+1]), v);
481485
}
482486
i += 2; // skip the next two arguments
487+
} else if (isoption(&text, 'o', "output-file", is_short)) {
488+
options |= NO_COLOR_OUTPUT;
489+
const char *which = "output-file";
490+
if (i >= argc - 1) {
491+
fprintf(stderr, "jq: --%s takes one parameter (e.g. --%s filename)\n", which, which);
492+
die();
493+
}
494+
495+
ofile = fopen(argv[i+1], "w");
496+
if (!ofile ) {
497+
fprintf(stderr, "jq: unable to open output-file.");
498+
die();
499+
}
500+
501+
i += 1; // skip the next argument
483502
} else if ((raw = isoption(&text, 0, "rawfile", is_short)) ||
484503
isoption(&text, 0, "slurpfile", is_short)) {
485504
const char *which = raw ? "rawfile" : "slurpfile";
@@ -696,8 +715,8 @@ int main(int argc, char* argv[]) {
696715
ret = JQ_ERROR_SYSTEM;
697716

698717
out:
699-
badwrite = ferror(stdout);
700-
if (fclose(stdout)!=0 || badwrite) {
718+
badwrite = ferror(ofile);
719+
if (fclose(ofile)!=0 || badwrite) {
701720
fprintf(stderr,"jq: error: writing output failed: %s\n", strerror(errno));
702721
ret = JQ_ERROR_SYSTEM;
703722
}

tests/shtest

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,10 @@ printf '[\n {\n "a": 1\n }\n]\n' > $d/expected
805805
$JQ --indent 6 -n "[{a:1}]" > $d/out
806806
cmp $d/out $d/expected
807807

808+
printf '{"wide":"👋"}' > $d/expected
809+
$JQ -j -c -o $d/out -n "{wide:\"👋\"}"
810+
cmp $d/out $d/expected
811+
808812
if ! $msys && ! $mingw; then
809813
# Test handling of timezones -- #2429, #2475
810814
if ! r=$(TZ=Asia/Tokyo $JQ -rn '1731627341 | strflocaltime("%F %T %z %Z")') \

0 commit comments

Comments
 (0)