psql NUL record and field separator
Inspired by this question http://stackoverflow.com/questions/6857265 I
have implemented a way to set the psql record and field separators to a
zero byte (ASCII NUL character). This can be very useful in shell
scripts to have an unambiguous separator. Other GNU tools such as find,
grep, sort, xargs also support this. So with this you could for example
do
psql --record-separator-zero -At -c 'select something from somewhere' | xargs -0 dosomething
I have thought about two different ways to implement this. Attempt one
was to make the backslash command option parsing zero-byte proof top to
bottom by using PQExpBuffers, so you could then write \R '\000'. But
that turned out to be very invasive and complicated. And worst, you
couldn't use it from the command line, because psql -R '\000' doesn't
work (the octal escape syntax is not used on the command line).
So attempt two, which I present here, is to just have separate syntax to
set the separators to zero bytes. From the command line it would be
--record-separator-zero and --field-separator-zero, and from within psql
it would be \pset recordsep_zero and \pset fieldsep_zero. I don't care
much for the verbosity of this, so I'm still thinking about ways to
abbreviate this. I think the most common use of this would be to set
the record separator from the command line, so we could use a short
option such as -0 or -z for that.
Patch attached. Comments welcome.
Attachments:
psql-nul-sep.patchtext/x-patch; charset=UTF-8; name=psql-nul-sep.patchDownload
diff --git i/doc/src/sgml/ref/psql-ref.sgml w/doc/src/sgml/ref/psql-ref.sgml
index a9b1ed2..752d6de 100644
--- i/doc/src/sgml/ref/psql-ref.sgml
+++ w/doc/src/sgml/ref/psql-ref.sgml
@@ -193,6 +193,15 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>--field-separator-zero</option></term>
+ <listitem>
+ <para>
+ Set the field separator for unaligned output to a zero byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-h <replaceable class="parameter">hostname</replaceable></></term>
<term><option>--host=<replaceable class="parameter">hostname</replaceable></></term>
<listitem>
@@ -320,6 +329,16 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>--record-separator-zero</option></term>
+ <listitem>
+ <para>
+ Set the record separator for unaligned output to a zero byte. This is
+ useful for interfacing, for example, with <literal>xargs -0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-s</></term>
<term><option>--single-step</></term>
<listitem>
@@ -1909,6 +1928,16 @@ lo_import 152801
</varlistentry>
<varlistentry>
+ <term><literal>fieldsep_zero</literal></term>
+ <listitem>
+ <para>
+ Sets the field separator to use in unaligned output format to a zero
+ byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>footer</literal></term>
<listitem>
<para>
@@ -2078,6 +2107,16 @@ lo_import 152801
</varlistentry>
<varlistentry>
+ <term><literal>recordsep_zero</literal></term>
+ <listitem>
+ <para>
+ Sets the record separator to use in unaligned output format to a zero
+ byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>tableattr</literal> (or <literal>T</literal>)</term>
<listitem>
<para>
diff --git i/src/bin/psql/command.c w/src/bin/psql/command.c
index 69fac83..9421a73 100644
--- i/src/bin/psql/command.c
+++ w/src/bin/psql/command.c
@@ -2284,11 +2284,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
if (value)
{
- free(popt->topt.fieldSep);
- popt->topt.fieldSep = pg_strdup(value);
+ free(popt->topt.fieldSep.separator);
+ popt->topt.fieldSep.separator = pg_strdup(value);
+ popt->topt.fieldSep.separator_zero = false;
}
if (!quiet)
- printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep);
+ {
+ if (popt->topt.fieldSep.separator_zero)
+ printf(_("Field separator is zero byte.\n"));
+ else
+ printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep.separator);
+ }
+ }
+
+ else if (strcmp(param, "fieldsep_zero") == 0)
+ {
+ free(popt->topt.fieldSep.separator);
+ popt->topt.fieldSep.separator = NULL;
+ popt->topt.fieldSep.separator_zero = true;
+ if (!quiet)
+ printf(_("Field separator is zero byte.\n"));
}
/* record separator for unaligned text */
@@ -2296,18 +2311,30 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
if (value)
{
- free(popt->topt.recordSep);
- popt->topt.recordSep = pg_strdup(value);
+ free(popt->topt.recordSep.separator);
+ popt->topt.recordSep.separator = pg_strdup(value);
+ popt->topt.recordSep.separator_zero = false;
}
if (!quiet)
{
- if (strcmp(popt->topt.recordSep, "\n") == 0)
+ if (popt->topt.recordSep.separator_zero)
+ printf(_("Record separator is zero byte.\n"));
+ else if (strcmp(popt->topt.recordSep.separator, "\n") == 0)
printf(_("Record separator is <newline>."));
else
- printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep);
+ printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep.separator);
}
}
+ else if (strcmp(param, "recordsep_zero") == 0)
+ {
+ free(popt->topt.recordSep.separator);
+ popt->topt.recordSep.separator = NULL;
+ popt->topt.recordSep.separator_zero = true;
+ if (!quiet)
+ printf(_("Record separator is zero byte.\n"));
+ }
+
/* toggle between full and tuples-only format */
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
diff --git i/src/bin/psql/help.c w/src/bin/psql/help.c
index 172fd0c..fbf2f76 100644
--- i/src/bin/psql/help.c
+++ w/src/bin/psql/help.c
@@ -116,10 +116,14 @@ usage(void)
printf(_(" -F, --field-separator=STRING\n"
" set field separator (default: \"%s\")\n"),
DEFAULT_FIELD_SEP);
+ printf(_(" --field-separator-zero\n"
+ " set field separator to zero byte\n"));
printf(_(" -H, --html HTML table output mode\n"));
printf(_(" -P, --pset=VAR[=ARG] set printing option VAR to ARG (see \\pset command)\n"));
printf(_(" -R, --record-separator=STRING\n"
" set record separator (default: newline)\n"));
+ printf(_(" --record-separator-zero\n"
+ " set record separator to zero byte\n"));
printf(_(" -t, --tuples-only print rows only\n"));
printf(_(" -T, --table-attr=TEXT set HTML table tag attributes (e.g., width, border)\n"));
printf(_(" -x, --expanded turn on expanded table output\n"));
@@ -237,8 +241,8 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\H toggle HTML output mode (currently %s)\n"),
ON(pset.popt.topt.format == PRINT_HTML));
fprintf(output, _(" \\pset NAME [VALUE] set table output option\n"
- " (NAME := {format|border|expanded|fieldsep|footer|null|\n"
- " numericlocale|recordsep|tuples_only|title|tableattr|pager})\n"));
+ " (NAME := {format|border|expanded|fieldsep|fieldsep_zero|footer|null|\n"
+ " numericlocale|recordsep|recordsep_zero|tuples_only|title|tableattr|pager})\n"));
fprintf(output, _(" \\t [on|off] show only rows (currently %s)\n"),
ON(pset.popt.topt.tuples_only));
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
diff --git i/src/bin/psql/print.c w/src/bin/psql/print.c
index e127edb..43ddab1 100644
--- i/src/bin/psql/print.c
+++ w/src/bin/psql/print.c
@@ -268,6 +268,16 @@ fputnbytes(FILE *f, const char *str, size_t n)
}
+static void
+print_separator(struct separator sep, FILE *fout)
+{
+ if (sep.separator_zero)
+ fputc('\000', fout);
+ else if (sep.separator)
+ fputs(sep.separator, fout);
+}
+
+
/*************************/
/* Unaligned text */
/*************************/
@@ -276,8 +286,6 @@ fputnbytes(FILE *f, const char *str, size_t n)
static void
print_unaligned_text(const printTableContent *cont, FILE *fout)
{
- const char *opt_fieldsep = cont->opt->fieldSep;
- const char *opt_recordsep = cont->opt->recordSep;
bool opt_tuples_only = cont->opt->tuples_only;
unsigned int i;
const char *const * ptr;
@@ -286,16 +294,14 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
if (cancel_pressed)
return;
- if (!opt_fieldsep)
- opt_fieldsep = "";
- if (!opt_recordsep)
- opt_recordsep = "";
-
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
- fprintf(fout, "%s%s", cont->title, opt_recordsep);
+ {
+ fputs(cont->title, fout);
+ print_separator(cont->opt->recordSep, fout);
+ }
/* print headers */
if (!opt_tuples_only)
@@ -303,7 +309,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
for (ptr = cont->headers; *ptr; ptr++)
{
if (ptr != cont->headers)
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
}
need_recordsep = true;
@@ -318,7 +324,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
{
if (need_recordsep)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
@@ -326,7 +332,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
fputs(*ptr, fout);
if ((i + 1) % cont->ncolumns)
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
else
need_recordsep = true;
}
@@ -342,7 +348,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
{
if (need_recordsep)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
}
fputs(f->data, fout);
@@ -359,8 +365,6 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
static void
print_unaligned_vertical(const printTableContent *cont, FILE *fout)
{
- const char *opt_fieldsep = cont->opt->fieldSep;
- const char *opt_recordsep = cont->opt->recordSep;
bool opt_tuples_only = cont->opt->tuples_only;
unsigned int i;
const char *const * ptr;
@@ -369,11 +373,6 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
if (cancel_pressed)
return;
- if (!opt_fieldsep)
- opt_fieldsep = "";
- if (!opt_recordsep)
- opt_recordsep = "";
-
if (cont->opt->start_table)
{
/* print title */
@@ -393,19 +392,19 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
if (need_recordsep)
{
/* record separator is 2 occurrences of recordsep in this mode */
- fputs(opt_recordsep, fout);
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
}
fputs(cont->headers[i % cont->ncolumns], fout);
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
if ((i + 1) % cont->ncolumns)
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
else
need_recordsep = true;
}
@@ -417,10 +416,10 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
{
printTableFooter *f;
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
for (f = cont->footers; f; f = f->next)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
fputs(f->data, fout);
}
}
diff --git i/src/bin/psql/print.h w/src/bin/psql/print.h
index 86c6e75..931535e 100644
--- i/src/bin/psql/print.h
+++ w/src/bin/psql/print.h
@@ -67,6 +67,12 @@ typedef struct printTextFormat
* marks when border=0? */
} printTextFormat;
+struct separator
+{
+ char *separator;
+ bool separator_zero;
+};
+
typedef struct printTableOpt
{
enum printFormat format; /* see enum above */
@@ -81,8 +87,8 @@ typedef struct printTableOpt
bool stop_table; /* print stop decoration, eg </table> */
unsigned long prior_records; /* start offset for record counters */
const printTextFormat *line_style; /* line style (NULL for default) */
- char *fieldSep; /* field separator for unaligned text mode */
- char *recordSep; /* record separator for unaligned text mode */
+ struct separator fieldSep; /* field separator for unaligned text mode */
+ struct separator recordSep; /* record separator for unaligned text mode */
bool numericLocale; /* locale-aware numeric units separator and
* decimal marker */
char *tableAttr; /* attributes for HTML <table ...> */
diff --git i/src/bin/psql/startup.c w/src/bin/psql/startup.c
index 8b1864c..69207c1 100644
--- i/src/bin/psql/startup.c
+++ w/src/bin/psql/startup.c
@@ -150,10 +150,16 @@ main(int argc, char *argv[])
parse_psql_options(argc, argv, &options);
- if (!pset.popt.topt.fieldSep)
- pset.popt.topt.fieldSep = pg_strdup(DEFAULT_FIELD_SEP);
- if (!pset.popt.topt.recordSep)
- pset.popt.topt.recordSep = pg_strdup(DEFAULT_RECORD_SEP);
+ if (!pset.popt.topt.fieldSep.separator)
+ {
+ pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP);
+ pset.popt.topt.fieldSep.separator_zero = false;
+ }
+ if (!pset.popt.topt.recordSep.separator)
+ {
+ pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP);
+ pset.popt.topt.recordSep.separator_zero = false;
+ }
if (options.username == NULL)
password_prompt = pg_strdup(_("Password: "));
@@ -338,6 +344,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
{"echo-hidden", no_argument, NULL, 'E'},
{"file", required_argument, NULL, 'f'},
{"field-separator", required_argument, NULL, 'F'},
+ {"field-separator-zero", no_argument, NULL, 10},
{"host", required_argument, NULL, 'h'},
{"html", no_argument, NULL, 'H'},
{"list", no_argument, NULL, 'l'},
@@ -349,6 +356,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
{"pset", required_argument, NULL, 'P'},
{"quiet", no_argument, NULL, 'q'},
{"record-separator", required_argument, NULL, 'R'},
+ {"record-separator-zero", no_argument, NULL, 11},
{"single-step", no_argument, NULL, 's'},
{"single-line", no_argument, NULL, 'S'},
{"tuples-only", no_argument, NULL, 't'},
@@ -407,7 +415,11 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
options->action_string = optarg;
break;
case 'F':
- pset.popt.topt.fieldSep = pg_strdup(optarg);
+ pset.popt.topt.fieldSep.separator = pg_strdup(optarg);
+ pset.popt.topt.fieldSep.separator_zero = false;
+ break;
+ case 10:
+ pset.popt.topt.fieldSep.separator_zero = true;
break;
case 'h':
options->host = optarg;
@@ -459,7 +471,11 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
SetVariableBool(pset.vars, "QUIET");
break;
case 'R':
- pset.popt.topt.recordSep = pg_strdup(optarg);
+ pset.popt.topt.recordSep.separator = pg_strdup(optarg);
+ pset.popt.topt.recordSep.separator_zero = false;
+ break;
+ case 11:
+ pset.popt.topt.recordSep.separator_zero = true;
break;
case 's':
SetVariableBool(pset.vars, "SINGLESTEP");
At 2012-01-14 14:23:49 +0200, peter_e@gmx.net wrote:
Inspired by this question http://stackoverflow.com/questions/6857265 I
have implemented a way to set the psql record and field separators to
a zero byte (ASCII NUL character).
Since this patch is in the commitfest, I had a look at it.
I agree that the feature is useful. The patch applies and builds cleanly
with HEAD@9f9135d1, but needs a further minor tweak to work (attached).
Without it, both zero separators get overwritten with the default value
after option parsing. The code looks good otherwise.
There's one problem:
psql --record-separator-zero -At -c 'select something from somewhere' | xargs -0 dosomething
If you run find -print0 and it finds one file, it will still print
"filename\0", and xargs -0 will work fine.
But psql --record-separator-zero -At -c 'select 1' will print "1\n", not
"1\0" or even "1\0\n", so xargs -0 will use the value "1\n", not "1". If
you're doing this in a shell script, handing the last argument specially
would be painful.
At issue are (at least) these three lines from print_unaligned_text in
src/bin/psql/print.c:
358 /* the last record needs to be concluded with a newline */
359 if (need_recordsep)
360 fputc('\n', fout);
Perhaps the right thing to do would be to change this to output \0 if
--record-separator-zero was used (but leave it at \n otherwise)? That
is what my second attached patch does:
$ bin/psql --record-separator-zero --field-separator-zero -At -c 'select 1,2 union select 3,4'|xargs -0 echo
1 2 3 4
Thoughts?
I think the most common use of this would be to set the record
separator from the command line, so we could use a short option
such as -0 or -z for that.
I agree. The current option names are very unwieldy to type.
-- ams
Attachments:
petere-zero-fix.difftext/x-diff; charset=us-asciiDownload
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 69207c1..691ad14 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -150,12 +150,14 @@ main(int argc, char *argv[])
parse_psql_options(argc, argv, &options);
- if (!pset.popt.topt.fieldSep.separator)
+ if (!pset.popt.topt.fieldSep.separator &&
+ !pset.popt.topt.fieldSep.separator_zero)
{
pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP);
pset.popt.topt.fieldSep.separator_zero = false;
}
- if (!pset.popt.topt.recordSep.separator)
+ if (!pset.popt.topt.recordSep.separator &&
+ !pset.popt.topt.recordSep.separator_zero)
{
pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP);
pset.popt.topt.recordSep.separator_zero = false;
petere-zero-lastrecord.difftext/x-diff; charset=us-asciiDownload
diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c
index 43ddab1..94535eb 100644
--- a/src/bin/psql/print.c
+++ b/src/bin/psql/print.c
@@ -357,7 +357,12 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
}
/* the last record needs to be concluded with a newline */
if (need_recordsep)
- fputc('\n', fout);
+ {
+ if (cont->opt->recordSep.separator_zero)
+ print_separator(cont->opt->recordSep, fout);
+ else
+ fputc('\n', fout);
+ }
}
}
On tor, 2012-01-26 at 19:00 +0530, Abhijit Menon-Sen wrote:
At issue are (at least) these three lines from print_unaligned_text in
src/bin/psql/print.c:358 /* the last record needs to be concluded with a newline
*/
359 if (need_recordsep)
360 fputc('\n', fout);Perhaps the right thing to do would be to change this to output \0 if
--record-separator-zero was used (but leave it at \n otherwise)? That
is what my second attached patch does:$ bin/psql --record-separator-zero --field-separator-zero -At -c
'select 1,2 union select 3,4'|xargs -0 echo
1 2 3 4Thoughts?
I think the most common use of this would be to set the record
separator from the command line, so we could use a short option
such as -0 or -z for that.I agree. The current option names are very unwieldy to type.
I have incorporated your two patches and added short options. Updated
patch attached.
This made me wonder, however. The existing -F and -R options set the
record *separator*. The new options, however, set the record
*terminator*. This is the small distinction that you had discovered.
Should we rename the options and/or add that to the documentation, or is
the new behavior obvious and any new terminology would be too confusing?
Attachments:
psql-nul-sep.patchtext/x-patch; charset=UTF-8; name=psql-nul-sep.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index a9b1ed2..55aa5f2 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -482,6 +482,27 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-z</option></term>
+ <term><option>--field-separator-zero</option></term>
+ <listitem>
+ <para>
+ Set the field separator for unaligned output to a zero byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-0</option></term>
+ <term><option>--record-separator-zero</option></term>
+ <listitem>
+ <para>
+ Set the record separator for unaligned output to a zero byte. This is
+ useful for interfacing, for example, with <literal>xargs -0</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-1</option></term>
<term><option>--single-transaction</option></term>
@@ -1909,6 +1930,16 @@ lo_import 152801
</varlistentry>
<varlistentry>
+ <term><literal>fieldsep_zero</literal></term>
+ <listitem>
+ <para>
+ Sets the field separator to use in unaligned output format to a zero
+ byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>footer</literal></term>
<listitem>
<para>
@@ -2078,6 +2109,16 @@ lo_import 152801
</varlistentry>
<varlistentry>
+ <term><literal>recordsep_zero</literal></term>
+ <listitem>
+ <para>
+ Sets the record separator to use in unaligned output format to a zero
+ byte.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>tableattr</literal> (or <literal>T</literal>)</term>
<listitem>
<para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index ab809ec..8421ad0 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2272,11 +2272,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
if (value)
{
- free(popt->topt.fieldSep);
- popt->topt.fieldSep = pg_strdup(value);
+ free(popt->topt.fieldSep.separator);
+ popt->topt.fieldSep.separator = pg_strdup(value);
+ popt->topt.fieldSep.separator_zero = false;
}
if (!quiet)
- printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep);
+ {
+ if (popt->topt.fieldSep.separator_zero)
+ printf(_("Field separator is zero byte.\n"));
+ else
+ printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep.separator);
+ }
+ }
+
+ else if (strcmp(param, "fieldsep_zero") == 0)
+ {
+ free(popt->topt.fieldSep.separator);
+ popt->topt.fieldSep.separator = NULL;
+ popt->topt.fieldSep.separator_zero = true;
+ if (!quiet)
+ printf(_("Field separator is zero byte.\n"));
}
/* record separator for unaligned text */
@@ -2284,18 +2299,30 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
if (value)
{
- free(popt->topt.recordSep);
- popt->topt.recordSep = pg_strdup(value);
+ free(popt->topt.recordSep.separator);
+ popt->topt.recordSep.separator = pg_strdup(value);
+ popt->topt.recordSep.separator_zero = false;
}
if (!quiet)
{
- if (strcmp(popt->topt.recordSep, "\n") == 0)
+ if (popt->topt.recordSep.separator_zero)
+ printf(_("Record separator is zero byte.\n"));
+ else if (strcmp(popt->topt.recordSep.separator, "\n") == 0)
printf(_("Record separator is <newline>."));
else
- printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep);
+ printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep.separator);
}
}
+ else if (strcmp(param, "recordsep_zero") == 0)
+ {
+ free(popt->topt.recordSep.separator);
+ popt->topt.recordSep.separator = NULL;
+ popt->topt.recordSep.separator_zero = true;
+ if (!quiet)
+ printf(_("Record separator is zero byte.\n"));
+ }
+
/* toggle between full and tuples-only format */
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 172fd0c..eff0ea5 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -123,6 +123,10 @@ usage(void)
printf(_(" -t, --tuples-only print rows only\n"));
printf(_(" -T, --table-attr=TEXT set HTML table tag attributes (e.g., width, border)\n"));
printf(_(" -x, --expanded turn on expanded table output\n"));
+ printf(_(" -z, --field-separator-zero\n"
+ " set field separator to zero byte\n"));
+ printf(_(" -0, --record-separator-zero\n"
+ " set record separator to zero byte\n"));
printf(_("\nConnection options:\n"));
/* Display default host */
@@ -237,8 +241,8 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\H toggle HTML output mode (currently %s)\n"),
ON(pset.popt.topt.format == PRINT_HTML));
fprintf(output, _(" \\pset NAME [VALUE] set table output option\n"
- " (NAME := {format|border|expanded|fieldsep|footer|null|\n"
- " numericlocale|recordsep|tuples_only|title|tableattr|pager})\n"));
+ " (NAME := {format|border|expanded|fieldsep|fieldsep_zero|footer|null|\n"
+ " numericlocale|recordsep|recordsep_zero|tuples_only|title|tableattr|pager})\n"));
fprintf(output, _(" \\t [on|off] show only rows (currently %s)\n"),
ON(pset.popt.topt.tuples_only));
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c
index e127edb..dec440c 100644
--- a/src/bin/psql/print.c
+++ b/src/bin/psql/print.c
@@ -268,6 +268,16 @@ fputnbytes(FILE *f, const char *str, size_t n)
}
+static void
+print_separator(struct separator sep, FILE *fout)
+{
+ if (sep.separator_zero)
+ fputc('\000', fout);
+ else if (sep.separator)
+ fputs(sep.separator, fout);
+}
+
+
/*************************/
/* Unaligned text */
/*************************/
@@ -276,8 +286,6 @@ fputnbytes(FILE *f, const char *str, size_t n)
static void
print_unaligned_text(const printTableContent *cont, FILE *fout)
{
- const char *opt_fieldsep = cont->opt->fieldSep;
- const char *opt_recordsep = cont->opt->recordSep;
bool opt_tuples_only = cont->opt->tuples_only;
unsigned int i;
const char *const * ptr;
@@ -286,16 +294,14 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
if (cancel_pressed)
return;
- if (!opt_fieldsep)
- opt_fieldsep = "";
- if (!opt_recordsep)
- opt_recordsep = "";
-
if (cont->opt->start_table)
{
/* print title */
if (!opt_tuples_only && cont->title)
- fprintf(fout, "%s%s", cont->title, opt_recordsep);
+ {
+ fputs(cont->title, fout);
+ print_separator(cont->opt->recordSep, fout);
+ }
/* print headers */
if (!opt_tuples_only)
@@ -303,7 +309,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
for (ptr = cont->headers; *ptr; ptr++)
{
if (ptr != cont->headers)
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
}
need_recordsep = true;
@@ -318,7 +324,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
{
if (need_recordsep)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
@@ -326,7 +332,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
fputs(*ptr, fout);
if ((i + 1) % cont->ncolumns)
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
else
need_recordsep = true;
}
@@ -342,16 +348,25 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
{
if (need_recordsep)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
}
fputs(f->data, fout);
need_recordsep = true;
}
}
- /* the last record needs to be concluded with a newline */
+ /*
+ * The last record is terminated by a newline, independent of the set
+ * record separator. But when the record separator is a zero byte, we
+ * use that (compatible with find -print0 and xargs).
+ */
if (need_recordsep)
- fputc('\n', fout);
+ {
+ if (cont->opt->recordSep.separator_zero)
+ print_separator(cont->opt->recordSep, fout);
+ else
+ fputc('\n', fout);
+ }
}
}
@@ -359,8 +374,6 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
static void
print_unaligned_vertical(const printTableContent *cont, FILE *fout)
{
- const char *opt_fieldsep = cont->opt->fieldSep;
- const char *opt_recordsep = cont->opt->recordSep;
bool opt_tuples_only = cont->opt->tuples_only;
unsigned int i;
const char *const * ptr;
@@ -369,11 +382,6 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
if (cancel_pressed)
return;
- if (!opt_fieldsep)
- opt_fieldsep = "";
- if (!opt_recordsep)
- opt_recordsep = "";
-
if (cont->opt->start_table)
{
/* print title */
@@ -393,19 +401,19 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
if (need_recordsep)
{
/* record separator is 2 occurrences of recordsep in this mode */
- fputs(opt_recordsep, fout);
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
+ print_separator(cont->opt->recordSep, fout);
need_recordsep = false;
if (cancel_pressed)
break;
}
fputs(cont->headers[i % cont->ncolumns], fout);
- fputs(opt_fieldsep, fout);
+ print_separator(cont->opt->fieldSep, fout);
fputs(*ptr, fout);
if ((i + 1) % cont->ncolumns)
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
else
need_recordsep = true;
}
@@ -417,15 +425,19 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
{
printTableFooter *f;
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
for (f = cont->footers; f; f = f->next)
{
- fputs(opt_recordsep, fout);
+ print_separator(cont->opt->recordSep, fout);
fputs(f->data, fout);
}
}
- fputc('\n', fout);
+ /* see above in print_unaligned_text() */
+ if (cont->opt->recordSep.separator_zero)
+ print_separator(cont->opt->recordSep, fout);
+ else
+ fputc('\n', fout);
}
}
diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h
index 86c6e75..931535e 100644
--- a/src/bin/psql/print.h
+++ b/src/bin/psql/print.h
@@ -67,6 +67,12 @@ typedef struct printTextFormat
* marks when border=0? */
} printTextFormat;
+struct separator
+{
+ char *separator;
+ bool separator_zero;
+};
+
typedef struct printTableOpt
{
enum printFormat format; /* see enum above */
@@ -81,8 +87,8 @@ typedef struct printTableOpt
bool stop_table; /* print stop decoration, eg </table> */
unsigned long prior_records; /* start offset for record counters */
const printTextFormat *line_style; /* line style (NULL for default) */
- char *fieldSep; /* field separator for unaligned text mode */
- char *recordSep; /* record separator for unaligned text mode */
+ struct separator fieldSep; /* field separator for unaligned text mode */
+ struct separator recordSep; /* record separator for unaligned text mode */
bool numericLocale; /* locale-aware numeric units separator and
* decimal marker */
char *tableAttr; /* attributes for HTML <table ...> */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 8b1864c..aff5772 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -150,10 +150,18 @@ main(int argc, char *argv[])
parse_psql_options(argc, argv, &options);
- if (!pset.popt.topt.fieldSep)
- pset.popt.topt.fieldSep = pg_strdup(DEFAULT_FIELD_SEP);
- if (!pset.popt.topt.recordSep)
- pset.popt.topt.recordSep = pg_strdup(DEFAULT_RECORD_SEP);
+ if (!pset.popt.topt.fieldSep.separator &&
+ !pset.popt.topt.fieldSep.separator_zero)
+ {
+ pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP);
+ pset.popt.topt.fieldSep.separator_zero = false;
+ }
+ if (!pset.popt.topt.recordSep.separator &&
+ !pset.popt.topt.recordSep.separator_zero)
+ {
+ pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP);
+ pset.popt.topt.recordSep.separator_zero = false;
+ }
if (options.username == NULL)
password_prompt = pg_strdup(_("Password: "));
@@ -338,6 +346,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
{"echo-hidden", no_argument, NULL, 'E'},
{"file", required_argument, NULL, 'f'},
{"field-separator", required_argument, NULL, 'F'},
+ {"field-separator-zero", no_argument, NULL, 'z'},
{"host", required_argument, NULL, 'h'},
{"html", no_argument, NULL, 'H'},
{"list", no_argument, NULL, 'l'},
@@ -349,6 +358,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
{"pset", required_argument, NULL, 'P'},
{"quiet", no_argument, NULL, 'q'},
{"record-separator", required_argument, NULL, 'R'},
+ {"record-separator-zero", no_argument, NULL, '0'},
{"single-step", no_argument, NULL, 's'},
{"single-line", no_argument, NULL, 'S'},
{"tuples-only", no_argument, NULL, 't'},
@@ -372,7 +382,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
memset(options, 0, sizeof *options);
- while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:HlL:no:p:P:qR:sStT:U:v:VwWxX?1",
+ while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:HlL:no:p:P:qR:sStT:U:v:VwWxXz?01",
long_options, &optindex)) != -1)
{
switch (c)
@@ -407,7 +417,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
options->action_string = optarg;
break;
case 'F':
- pset.popt.topt.fieldSep = pg_strdup(optarg);
+ pset.popt.topt.fieldSep.separator = pg_strdup(optarg);
+ pset.popt.topt.fieldSep.separator_zero = false;
break;
case 'h':
options->host = optarg;
@@ -459,7 +470,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
SetVariableBool(pset.vars, "QUIET");
break;
case 'R':
- pset.popt.topt.recordSep = pg_strdup(optarg);
+ pset.popt.topt.recordSep.separator = pg_strdup(optarg);
+ pset.popt.topt.recordSep.separator_zero = false;
break;
case 's':
SetVariableBool(pset.vars, "SINGLESTEP");
@@ -521,6 +533,12 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
case 'X':
options->no_psqlrc = true;
break;
+ case 'z':
+ pset.popt.topt.fieldSep.separator_zero = true;
+ break;
+ case '0':
+ pset.popt.topt.recordSep.separator_zero = true;
+ break;
case '1':
options->single_txn = true;
break;
At 2012-02-07 13:20:43 +0200, peter_e@gmx.net wrote:
Should we rename the options and/or add that to the documentation, or is
the new behavior obvious and any new terminology would be too confusing?
I agree there is potential for confusion either way. I tried to come up
with a complete and not-confusing wording for all four options, but did
not manage to improve on the current state of affairs significantly. I
think it can stay the way it is. The reference to xargs -0 is probably
enough to set the right expectations about how it works.
We can always add a sentence later to clarify the special-case behaviour
of -0 if anyone is actually confused (and the best wording will be more
clear in that situation too).
-- Abhijit