]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Move the ".output" command into a separate subroutine. Enhance .output so
authordrh <>
Tue, 11 Nov 2025 14:08:50 +0000 (14:08 +0000)
committerdrh <>
Tue, 11 Nov 2025 14:08:50 +0000 (14:08 +0000)
that it is able to capture output in a sqlite3_str object, then compare that
captured output we patterns to accomplish tests.

FossilOrigin-Name: ddd167044753f5215624fc9ee0e3657836c528f23a6a3e262401fd1b64cdbb21

ext/misc/sqlite3_stdio.c
ext/misc/sqlite3_stdio.h
manifest
manifest.uuid
src/shell.c.in
test/shell1.test

index c9bceb194293dac773ed4396f8b00a4c1a6a4e3f..d597575265ba2f93ef9daf42dc3fd95d8d4693a4 100644 (file)
@@ -258,7 +258,7 @@ int sqlite3_fputs(const char *z, FILE *out){
 
 
 /*
-** Work-alike for fprintf() from the standard C library.
+** Work-alikes for fprintf() and vfprintf() from the standard C library.
 */
 int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
   int rc;
@@ -285,6 +285,24 @@ int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
   }
   return rc;
 }
+int sqlite3_vfprintf(FILE *out, const char *zFormat, va_list ap){
+  int rc;
+  if( UseWtextForOutput(out) ){
+    /* When writing to the command-prompt in Windows, it is necessary
+    ** to use _O_WTEXT input mode and write UTF-16 characters.
+    */
+    char *z;
+    z = sqlite3_vmprintf(zFormat, ap);
+    sqlite3_fputs(z, out);
+    rc = (int)strlen(z);
+    sqlite3_free(z);
+  }else{
+    /* Writing to a file or other destination, just write bytes without
+    ** any translation. */
+    rc = vfprintf(out, zFormat, ap);
+  }
+  return rc;
+}
 
 /*
 ** Set the mode for an output stream.  mode argument is typically _O_BINARY or
index dd0eefad043beab3afb7b9b8beb921377c7ed97a..75368df9f8696274ec60a4caf3b434b48197d5cc 100644 (file)
@@ -31,6 +31,7 @@
 #ifdef _WIN32
 /**** Definitions For Windows ****/
 #include <stdio.h>
+#include <stdarg.h>
 #include <windows.h>
 
 FILE *sqlite3_fopen(const char *zFilename, const char *zMode);
@@ -38,6 +39,7 @@ FILE *sqlite3_popen(const char *zCommand, const char *type);
 char *sqlite3_fgets(char *s, int size, FILE *stream);
 int sqlite3_fputs(const char *s, FILE *stream);
 int sqlite3_fprintf(FILE *stream, const char *format, ...);
+int sqlite3_vfprintf(FILE *stream, const char *format, va_list);
 void sqlite3_fsetmode(FILE *stream, int mode);
 
 
@@ -49,6 +51,7 @@ void sqlite3_fsetmode(FILE *stream, int mode);
 #define sqlite3_fgets     fgets
 #define sqlite3_fputs     fputs
 #define sqlite3_fprintf   fprintf
+#define sqlite3_vfprintf  vfprintf
 #define sqlite3_fsetmode(F,X)   /*no-op*/
 
 #endif
index 7382392ac0b41a80d9d83fe154accfecb80f624f..86c1ffaa9571ff53231eaf592e67bab27ac7b568 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Factor\sthe\scode\sfor\s".mode"\sout\sof\sdo_meta_command()\sand\sinto\sits\sown\nsubroutine,\sto\sshow\show\sthe\snew\sinfrastructure\smakes\sthis\seasy.\s\sWe\sneed\nto\sdo\sthe\ssame\sfor\sall\sthe\sdot-commands.\s\sAt\sthe\ssame\stime,\sadd\sthe\n-textjsonb\soption\sto\s.mode.
-D 2025-11-11T00:44:25.681
+C Move\sthe\s".output"\scommand\sinto\sa\sseparate\ssubroutine.\s\sEnhance\s.output\sso\nthat\sit\sis\sable\sto\scapture\soutput\sin\sa\ssqlite3_str\sobject,\sthen\scompare\sthat\ncaptured\soutput\swe\spatterns\sto\saccomplish\stests.
+D 2025-11-11T14:08:50.512
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -397,8 +397,8 @@ F ext/misc/shathree.c fd22d70620f86a0467acfdd3acd8435d5cb54eb1e2d9ff36ae44e38982
 F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 F ext/misc/spellfix.c 693c8fd3293087fa821322967a97e59dfa24051e5d2ca7fa85790a4034db6fa4
 F ext/misc/sqlar.c a6175790482328171da47095f87608b48a476d4fac78d8a9ff18b03a2454f634
-F ext/misc/sqlite3_stdio.c 0fe5a45bd332b30aef2b68c64edbe69e31e9c42365b0fa79ce95a034bca6fbb0
-F ext/misc/sqlite3_stdio.h f05eaf5e0258f0573910324a789a9586fc360a57678c57a6d63cfaa2245b6176
+F ext/misc/sqlite3_stdio.c e49c07050bf7bdc87866da7583beda236f2f8c462018a34b61785d99cbddedfd
+F ext/misc/sqlite3_stdio.h 27a4ecea47e61bc9574ccdf2806f468afe23af2f95028c9b689bfa08ab1ce99f
 F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321
 F ext/misc/stmtrand.c 59cffa5d8e158943ff1ce078956d8e208e8c04e67307e8f249dece2436dcb7fc
 F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b
@@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a
-F src/shell.c.in e3f804207b35a78be6a72c794291b771de7fafdc319fce3f9f2979bbf14d7488
+F src/shell.c.in 6b289585d97c8d9e57efc8f589588566d88212c73e879806a09fdd03ce19acc0
 F src/sqlite.h.in 7403a952a8f1239de7525b73c4e3a0f9540ec0607ed24fec887f5832642d44b8
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52
@@ -1601,7 +1601,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test d70a7a741e0abae444576f963eab5eff13a2ef6580678d627fe405291ec353ee
+F test/shell1.test 4d44fb7d08a62b6c6424185e519d59fff9c93d9f9cbf4c773f30908bf60cb3de
 F test/shell2.test d8da6a06dcce1d8f04f776f918d4d57c28ddc28c54f3a44f95429794892e3a91
 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
 F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d
@@ -2173,8 +2173,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P a0fc99a3bdd12f9ac69511c1aea2bd9a3d5de593d44b4a732cfc8f48e0931c76
-R 89d6866f71ef4d7cd775ab4a25b5defa
+P b48aa054df488747a7db56faf1cd0da42e322edff60650b9187448e58eb12def
+R 5c9f64670d89ec287264fbae90780bfe
 U drh
-Z d05d0d77f90346dd26288eb7ce7ea406
+Z 419508d00d340c19a4153a9b54d8de2d
 # Remove this line to create a well-formed Fossil manifest.
index 8b673851dc294f65207fdb97eda6b86eb43fd6c4..5477ea85ab479ff8141928ddc9a1cb4899b58290 100644 (file)
@@ -1 +1 @@
-b48aa054df488747a7db56faf1cd0da42e322edff60650b9187448e58eb12def
+ddd167044753f5215624fc9ee0e3657836c528f23a6a3e262401fd1b64cdbb21
index 93d2ae414d051a52e865da3b688a55eeae37e89b..5fbd62014b7bceb71fdcf94edaa9e98b69405710 100644 (file)
@@ -254,8 +254,67 @@ INCLUDE ../ext/qrf/qrf.c
 # define SQLITE_CIO_NO_FLUSH
 #endif
 
-#define eputz(z) sqlite3_fputs(z,stderr)
-#define sputz(fp,z) sqlite3_fputs(z,fp)
+/*
+** Output routines that are able to redirect to memory rather than
+** doing actually I/O.
+**                                                Works like.
+**                                                --------------
+**   cli_printf(FILE*, const char*, ...);         fprintf()
+**   cli_puts(const char*, FILE*);                fputs()
+**   cli_vprintf(FILE*, const char*, va_list);    vfprintf()
+**
+** These are just thin wrappers with the following added semantics:
+** If the file-scope variable cli_output_capture is not NULL, and
+** if the FILE* argument is stdout or stderr, then rather than
+** writing to stdout/stdout, append the text to the cli_output_capture
+** variable.
+**
+** The cli_exit(int) routine works like exit() except that it
+** first dumps any capture output to stdout.
+*/
+static sqlite3_str *cli_output_capture = 0; 
+static int cli_printf(FILE *out, const char *zFormat, ...){
+  va_list ap;
+  int rc;
+  va_start(ap,zFormat);
+  if( cli_output_capture && (out==stdout || out==stderr) ){
+    sqlite3_str_vappendf(cli_output_capture, zFormat, ap);
+    rc = 1;
+  }else{
+    rc = sqlite3_vfprintf(out, zFormat, ap);
+  }
+  va_end(ap);
+  return rc;
+}
+static int cli_puts(const char *zText, FILE *out){
+  if( cli_output_capture && (out==stdout || out==stderr) ){
+    sqlite3_str_appendall(cli_output_capture, zText);
+    return 1;
+  }
+  return sqlite3_fputs(zText, out);
+}
+#if 0 /* Not currently used - available if we need it later */
+static int cli_vprintf(FILE *out, const char *zFormat, va_list ap){
+  if( cli_output_capture && (out==stdout || out==stderr) ){
+    sqlite3_str_vappendf(cli_output_capture, zFormat, ap);
+    return 1;
+  }else{
+    return sqlite3_vfprintf(out, zFormat, ap);
+  }
+}
+#endif
+static void cli_exit(int rc){
+  if( cli_output_capture ){
+    char *z = sqlite3_str_finish(cli_output_capture);
+    sqlite3_fputs(z, stdout);
+    fflush(stdout);
+  }
+  exit(rc);
+}
+
+
+#define eputz(z) cli_puts(z,stderr)
+#define sputz(fp,z) cli_puts(z,fp)
 
 /* True if the timer is enabled */
 static int enableTimer = 0;
@@ -346,7 +405,7 @@ static void endTimer(FILE *out){
     sqlite3_int64 iEnd = timeOfDay();
     struct rusage sEnd;
     getrusage(RUSAGE_SELF, &sEnd);
-    sqlite3_fprintf(out, "Run Time: real %.6f user %.6f sys %.6f\n",
+    cli_printf(out, "Run Time: real %.6f user %.6f sys %.6f\n",
           (iEnd - iBegin)*0.000001,
           timeDiff(&sBegin.ru_utime, &sEnd.ru_utime),
           timeDiff(&sBegin.ru_stime, &sEnd.ru_stime));
@@ -427,13 +486,13 @@ static void endTimer(FILE *out){
     getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd);
 #ifdef _WIN64
     /* microsecond precision on 64-bit windows */
-    sqlite3_fprintf(out, "Run Time: real %.6f user %f sys %f\n",
+    cli_printf(out, "Run Time: real %.6f user %f sys %f\n",
           (ftWallEnd - ftWallBegin)*0.000001,
           timeDiff(&ftUserBegin, &ftUserEnd),
           timeDiff(&ftKernelBegin, &ftKernelEnd));
 #else
     /* millisecond precisino on 32-bit windows */
-    sqlite3_fprintf(out, "Run Time: real %.3f user %.3f sys %.3f\n",
+    cli_printf(out, "Run Time: real %.3f user %.3f sys %.3f\n",
           (ftWallEnd - ftWallBegin)*0.000001,
           timeDiff(&ftUserBegin, &ftUserEnd),
           timeDiff(&ftKernelBegin, &ftKernelEnd));
@@ -617,7 +676,7 @@ static char *dynamicContinuePrompt(void){
 /* Indicate out-of-memory and exit. */
 static void shell_out_of_memory(void){
   eputz("Error: out of memory\n");
-  exit(1);
+  cli_exit(1);
 }
 
 /* Check a pointer to see if it is NULL.  If it is NULL, exit with an
@@ -648,7 +707,7 @@ static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){
   va_start(ap, zFormat);
   z = sqlite3_vmprintf(zFormat, ap);
   va_end(ap);
-  sqlite3_fprintf(iotrace, "%s", z);
+  cli_printf(iotrace, "%s", z);
   sqlite3_free(z);
 }
 #endif
@@ -1230,6 +1289,8 @@ struct ShellState {
   unsigned flgProgress;  /* Flags for the progress callback */
   unsigned shellFlgs;    /* Various flags */
   unsigned priorShFlgs;  /* Saved copy of flags */
+  unsigned nTestRun;     /* Number of test cases run */
+  unsigned nTestErr;     /* Number of test cases that failed */
   sqlite3_int64 szMax;   /* --maxsize argument to .open */
   char *zDestTable;      /* Name of destination table when MODE_Insert */
   char *zTempFile;       /* Temporary file that might need deleting */
@@ -1427,8 +1488,7 @@ static void modeInit(Mode *p){
 
 /*
 ** Clear a display mode, freeing any allocated memory that it
-** contains.  This also resets the display mode back to its
-** defaults.
+** contains.
 */
 static void modeFree(Mode *p){
   free(p->spec.aWidth);
@@ -1437,7 +1497,7 @@ static void modeFree(Mode *p){
   free(p->spec.zRowSep);
   free(p->spec.zTableName);
   free(p->spec.zNull);
-  modeInit(p);
+  memset(p, 0, sizeof(*p));
 }
 
 /*
@@ -1501,7 +1561,7 @@ static void modeSetStr(char **az, const char *zNew){
 static void shellLog(void *pArg, int iErrCode, const char *zMsg){
   ShellState *p = (ShellState*)pArg;
   if( p->pLog==0 ) return;
-  sqlite3_fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
+  cli_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
   fflush(p->pLog);
 }
 
@@ -1518,7 +1578,7 @@ static void shellPutsFunc(
 ){
   ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
   (void)nVal;
-  sqlite3_fprintf(p->out, "%s\n", sqlite3_value_text(apVal[0]));
+  cli_printf(p->out, "%s\n", sqlite3_value_text(apVal[0]));
   sqlite3_result_value(pCtx, apVal[0]);
 }
 
@@ -1552,8 +1612,8 @@ static void failIfSafeMode(
     va_start(ap, zErrMsg);
     zMsg = sqlite3_vmprintf(zErrMsg, ap);
     va_end(ap);
-    sqlite3_fprintf(stderr, "%s %s\n", zLoc, zMsg);
-    exit(1);
+    cli_printf(stderr, "%s %s\n", zLoc, zMsg);
+    cli_exit(1);
   }
 }
 
@@ -1572,11 +1632,11 @@ static void dotCmdError(
   if( zBrief!=0 && iArg>=0 && iArg<p->dot.nArg ){
     int i = p->dot.aiOfst[iArg];
     int nPrompt = strlen(zBrief) + 5;
-    sqlite3_fprintf(out, "%s %s\n", zLoc, p->dot.zOrig);
+    cli_printf(out, "%s %s\n", zLoc, p->dot.zOrig);
     if( i > nPrompt ){
-      sqlite3_fprintf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief);
+      cli_printf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief);
     }else{
-      sqlite3_fprintf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief);
+      cli_printf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief);
     }
   }
   if( zDetail ){
@@ -1585,7 +1645,7 @@ static void dotCmdError(
     va_start(ap, zDetail);
     zMsg = sqlite3_vmprintf(zDetail,ap);
     va_end(ap);
-    sqlite3_fprintf(out,"%s %s\n", zLoc, zMsg);
+    cli_printf(out,"%s %s\n", zLoc, zMsg);
     sqlite3_free(zMsg);
   }
   sqlite3_free(zLoc);
@@ -1829,13 +1889,13 @@ static void output_c_string(FILE *out, const char *z){
   static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */
   char ace[3] = "\\?";
   char cbsSay;
-  sqlite3_fputs(zq, out);
+  cli_puts(zq, out);
   while( *z!=0 ){
     const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0);
     const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask);
     const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast;
     if( pcEnd > z ){
-      sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z);
+      cli_printf(out, "%.*s", (int)(pcEnd-z), z);
     }
     if( (c = *pcEnd)==0 ) break;
     ++pcEnd;
@@ -1851,16 +1911,16 @@ static void output_c_string(FILE *out, const char *z){
     }
     if( cbsSay ){
       ace[1] = cbsSay;
-      sqlite3_fputs(ace, out);
+      cli_puts(ace, out);
     }else if( !isprint(c&0xff) ){
-      sqlite3_fprintf(out, "\\%03o", c&0xff);
+      cli_printf(out, "\\%03o", c&0xff);
     }else{
       ace[1] = (char)c;
-      sqlite3_fputs(ace+1, out);
+      cli_puts(ace+1, out);
     }
     z = pcEnd;
   }
-  sqlite3_fputs(zq, out);
+  cli_puts(zq, out);
 }
 
 /*
@@ -1868,7 +1928,7 @@ static void output_c_string(FILE *out, const char *z){
 */
 static void interrupt_handler(int NotUsed){
   UNUSED_PARAMETER(NotUsed);
-  if( ++seenInterrupt>1 ) exit(1);
+  if( ++seenInterrupt>1 ) cli_exit(1);
   if( globalDb ) sqlite3_interrupt(globalDb);
 }
 
@@ -1967,16 +2027,16 @@ static int shellAuth(
   az[1] = zA2;
   az[2] = zA3;
   az[3] = zA4;
-  sqlite3_fprintf(p->out, "authorizer: %s", azAction[op]);
+  cli_printf(p->out, "authorizer: %s", azAction[op]);
   for(i=0; i<4; i++){
-    sqlite3_fputs(" ", p->out);
+    cli_puts(" ", p->out);
     if( az[i] ){
       output_c_string(p->out, az[i]);
     }else{
-      sqlite3_fputs("NULL", p->out);
+      cli_puts("NULL", p->out);
     }
   }
-  sqlite3_fputs("\n", p->out);
+  cli_puts("\n", p->out);
   if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
   return SQLITE_OK;
 }
@@ -2014,9 +2074,9 @@ static void printSchemaLine(FILE *out, const char *z, const char *zTail){
     }
   }
   if( sqlite3_strglob("CREATE TABLE ['\"]*", z)==0 ){
-    sqlite3_fprintf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail);
+    cli_printf(out, "CREATE TABLE IF NOT EXISTS %s%s", z+13, zTail);
   }else{
-    sqlite3_fprintf(out, "%s%s", z, zTail);
+    cli_printf(out, "%s%s", z, zTail);
   }
   sqlite3_free(zToFree);
 }
@@ -2169,13 +2229,13 @@ static int progress_handler(void *pClientData) {
   ShellState *p = (ShellState*)pClientData;
   p->nProgress++;
   if( p->nProgress>=p->mxProgress && p->mxProgress>0 ){
-    sqlite3_fprintf(p->out, "Progress limit reached (%u)\n", p->nProgress);
+    cli_printf(p->out, "Progress limit reached (%u)\n", p->nProgress);
     if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
     if( p->flgProgress & SHELL_PROGRESS_ONCE ) p->mxProgress = 0;
     return 1;
   }
   if( (p->flgProgress & SHELL_PROGRESS_QUIET)==0 ){
-    sqlite3_fprintf(p->out, "Progress %u\n", p->nProgress);
+    cli_printf(p->out, "Progress %u\n", p->nProgress);
   }
   return 0;
 }
@@ -2240,7 +2300,7 @@ static void createSelftestTable(ShellState *p){
     "DROP TABLE [_shell$self];"
     ,0,0,&zErrMsg);
   if( zErrMsg ){
-    sqlite3_fprintf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg);
+    cli_printf(stderr, "SELFTEST initialization failure: %s\n", zErrMsg);
     sqlite3_free(zErrMsg);
   }
   sqlite3_exec(p->db, "RELEASE selftest_init",0,0,0);
@@ -2328,7 +2388,7 @@ static int run_table_dump_query(
   rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0);
   if( rc!=SQLITE_OK || !pSelect ){
     char *zContext = shell_error_context(zSelect, p->db);
-    sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n%s",
+    cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n%s",
           rc, sqlite3_errmsg(p->db), zContext);
     sqlite3_free(zContext);
     if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
@@ -2338,22 +2398,22 @@ static int run_table_dump_query(
   nResult = sqlite3_column_count(pSelect);
   while( rc==SQLITE_ROW ){
     z = (const char*)sqlite3_column_text(pSelect, 0);
-    sqlite3_fprintf(p->out, "%s", z);
+    cli_printf(p->out, "%s", z);
     for(i=1; i<nResult; i++){
-      sqlite3_fprintf(p->out, ",%s", sqlite3_column_text(pSelect, i));
+      cli_printf(p->out, ",%s", sqlite3_column_text(pSelect, i));
     }
     if( z==0 ) z = "";
     while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
     if( z[0] ){
-      sqlite3_fputs("\n;\n", p->out);
+      cli_puts("\n;\n", p->out);
     }else{
-      sqlite3_fputs(";\n", p->out);
+      cli_puts(";\n", p->out);
     }
     rc = sqlite3_step(pSelect);
   }
   rc = sqlite3_finalize(pSelect);
   if( rc!=SQLITE_OK ){
-    sqlite3_fprintf(p->out, "/**** ERROR: (%d) %s *****/\n",
+    cli_printf(p->out, "/**** ERROR: (%d) %s *****/\n",
                     rc, sqlite3_errmsg(p->db));
     if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
   }
@@ -2413,7 +2473,7 @@ static void displayLinuxIoStats(FILE *out){
     for(i=0; i<ArraySize(aTrans); i++){
       int n = strlen30(aTrans[i].zPattern);
       if( cli_strncmp(aTrans[i].zPattern, z, n)==0 ){
-        sqlite3_fprintf(out, "%-36s %s", aTrans[i].zDesc, &z[n]);
+        cli_printf(out, "%-36s %s", aTrans[i].zDesc, &z[n]);
         break;
       }
     }
@@ -2445,7 +2505,7 @@ static void displayStatLine(
   }else{
     sqlite3_snprintf(sizeof(zLine), zLine, zFormat, iHiwtr);
   }
-  sqlite3_fprintf(out, "%-36s %s\n", zLabel, zLine);
+  cli_printf(out, "%-36s %s\n", zLabel, zLine);
 }
 
 /*
@@ -2467,22 +2527,22 @@ static int display_stats(
     sqlite3_stmt *pStmt = pArg->pStmt;
     char z[100];
     nCol = sqlite3_column_count(pStmt);
-    sqlite3_fprintf(out, "%-36s %d\n", "Number of output columns:", nCol);
+    cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
     for(i=0; i<nCol; i++){
       sqlite3_snprintf(sizeof(z),z,"Column %d %nname:", i, &x);
-      sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i));
+      cli_printf(out, "%-36s %s\n", z, sqlite3_column_name(pStmt,i));
 #ifndef SQLITE_OMIT_DECLTYPE
       sqlite3_snprintf(30, z+x, "declared type:");
-      sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i));
+      cli_printf(out, "%-36s %s\n", z, sqlite3_column_decltype(pStmt, i));
 #endif
 #ifdef SQLITE_ENABLE_COLUMN_METADATA
       sqlite3_snprintf(30, z+x, "database name:");
-      sqlite3_fprintf(out, "%-36s %s\n", z,
+      cli_printf(out, "%-36s %s\n", z,
                            sqlite3_column_database_name(pStmt,i));
       sqlite3_snprintf(30, z+x, "table name:");
-      sqlite3_fprintf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i));
+      cli_printf(out, "%-36s %s\n", z, sqlite3_column_table_name(pStmt,i));
       sqlite3_snprintf(30, z+x, "origin name:");
-      sqlite3_fprintf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i));
+      cli_printf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i));
 #endif
     }
   }
@@ -2490,7 +2550,7 @@ static int display_stats(
   if( pArg->statsOn==3 ){
     if( pArg->pStmt ){
       iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset);
-      sqlite3_fprintf(out, "VM-steps: %d\n", iCur);
+      cli_printf(out, "VM-steps: %d\n", iCur);
     }
     return 0;
   }
@@ -2519,55 +2579,55 @@ static int display_stats(
       iHiwtr = iCur = -1;
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
                         &iCur, &iHiwtr, bReset);
-      sqlite3_fprintf(out, 
+      cli_printf(out, 
            "Lookaside Slots Used:                %d (max %d)\n", iCur, iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
                         &iCur, &iHiwtr, bReset);
-      sqlite3_fprintf(out,
+      cli_printf(out,
            "Successful lookaside attempts:       %d\n", iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
                         &iCur, &iHiwtr, bReset);
-      sqlite3_fprintf(out,
+      cli_printf(out,
            "Lookaside failures due to size:      %d\n", iHiwtr);
       sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
                         &iCur, &iHiwtr, bReset);
-      sqlite3_fprintf(out,
+      cli_printf(out,
            "Lookaside failures due to OOM:       %d\n", iHiwtr);
     }
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Pager Heap Usage:                    %d bytes\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Page cache hits:                     %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Page cache misses:                   %d\n", iCur);
     iHiwtr64 = iCur64 = -1;
     sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
                         0);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Page cache writes:                   %d\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Page cache spills:                   %d\n", iCur);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Temporary data spilled to disk:      %lld\n", iCur64);
     sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
                         1);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Schema Heap Usage:                   %d bytes\n", iCur);
     iHiwtr = iCur = -1;
     sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Statement Heap/Lookaside Usage:      %d bytes\n", iCur);
   }
 
@@ -2575,33 +2635,33 @@ static int display_stats(
     int iHit, iMiss;
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
                                bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Fullscan Steps:                      %d\n", iCur);
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Sort Operations:                     %d\n", iCur);
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Autoindex Inserts:                   %d\n", iCur);
     iHit = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_HIT,
                                bReset);
     iMiss = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FILTER_MISS,
                                 bReset);
     if( iHit || iMiss ){
-      sqlite3_fprintf(out,
+      cli_printf(out,
            "Bloom filter bypass taken:           %d/%d\n", iHit, iHit+iMiss);
     }
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Virtual Machine Steps:               %d\n", iCur);
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Reprepare operations:                %d\n", iCur);
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Number of times run:                 %d\n", iCur);
     iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_MEMUSED, bReset);
-    sqlite3_fprintf(out,
+    cli_printf(out,
            "Memory used by prepared stmt:        %d\n", iCur);
   }
 
@@ -2794,8 +2854,8 @@ static int expertFinish(
 
       if( bVerbose ){
         const char *zCand = sqlite3_expert_report(p,0,EXPERT_REPORT_CANDIDATES);
-        sqlite3_fputs("-- Candidates -----------------------------\n", out);
-        sqlite3_fprintf(out, "%s\n", zCand);
+        cli_puts("-- Candidates -----------------------------\n", out);
+        cli_printf(out, "%s\n", zCand);
       }
       for(i=0; i<nQuery; i++){
         const char *zSql = sqlite3_expert_report(p, i, EXPERT_REPORT_SQL);
@@ -2803,12 +2863,12 @@ static int expertFinish(
         const char *zEQP = sqlite3_expert_report(p, i, EXPERT_REPORT_PLAN);
         if( zIdx==0 ) zIdx = "(no new indexes)\n";
         if( bVerbose ){
-          sqlite3_fprintf(out,
+          cli_printf(out,
               "-- Query %d --------------------------------\n"
               "%s\n\n"
               ,i+1, zSql);
         }
-        sqlite3_fprintf(out, "%s\n%s\n", zIdx, zEQP);
+        cli_printf(out, "%s\n%s\n", zIdx, zEQP);
       }
     }
   }
@@ -2843,18 +2903,18 @@ static int expertDotCommand(
     }
     else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
       if( i==(nArg-1) ){
-        sqlite3_fprintf(stderr, "option requires an argument: %s\n", z);
+        cli_printf(stderr, "option requires an argument: %s\n", z);
         rc = SQLITE_ERROR;
       }else{
         iSample = (int)integerValue(azArg[++i]);
         if( iSample<0 || iSample>100 ){
-          sqlite3_fprintf(stderr,"value out of range: %s\n", azArg[i]);
+          cli_printf(stderr,"value out of range: %s\n", azArg[i]);
           rc = SQLITE_ERROR;
         }
       }
     }
     else{
-      sqlite3_fprintf(stderr,"unknown option: %s\n", z);
+      cli_printf(stderr,"unknown option: %s\n", z);
       rc = SQLITE_ERROR;
     }
   }
@@ -2862,7 +2922,7 @@ static int expertDotCommand(
   if( rc==SQLITE_OK ){
     pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
     if( pState->expert.pExpert==0 ){
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
           "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory");
       rc = SQLITE_ERROR;
     }else{
@@ -2882,7 +2942,7 @@ static int expertDotCommand(
 */
 static int shellWriteQR(void *pX, const char *z, sqlite3_int64 n){
   ShellState *pArg = (ShellState*)pX;
-  sqlite3_fprintf(pArg->out, "%.*s", (int)n, z);
+  cli_printf(pArg->out, "%.*s", (int)n, z);
   return SQLITE_OK;
 }
 
@@ -2995,12 +3055,12 @@ static int shell_exec(
         spec.eStyle = isExplain==1 ? QRF_STYLE_Explain : QRF_STYLE_Eqp;
         sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
       }else if( pArg->mode.eMode==MODE_Www ){
-        sqlite3_fprintf(pArg->out,
+        cli_printf(pArg->out,
               "</PRE>\n"
               "<TABLE border='1' cellspacing='0' cellpadding='2'>\n");
         spec.eStyle = QRF_STYLE_Html;
         sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
-        sqlite3_fprintf(pArg->out,
+        cli_printf(pArg->out,
               "</TABLE>\n"
               "<PRE>");
       }else{
@@ -3024,7 +3084,7 @@ static int shell_exec(
         sqlite3_reset(pStmt);
         rc = sqlite3_format_query_result(pStmt, &spec, &zErr);
         if( rc ){
-          sqlite3_fprintf(stderr, "Stats query failed: %s\n", zErr);
+          cli_printf(stderr, "Stats query failed: %s\n", zErr);
           sqlite3_free(zErr);
         }          
       }
@@ -3222,14 +3282,14 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
     */
     if( db_int(p->db, "SELECT count(*) FROM sqlite_sequence")>0 ){
       if( !p->writableSchema ){
-        sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out);
+        cli_puts("PRAGMA writable_schema=ON;\n", p->out);
         p->writableSchema = 1;
       }
-      sqlite3_fputs("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n"
+      cli_puts("CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq);\n"
                     "DELETE FROM sqlite_sequence;\n", p->out);
     }
   }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 && !noSys ){
-    if( !dataOnly ) sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out);
+    if( !dataOnly ) cli_puts("ANALYZE sqlite_schema;\n", p->out);
   }else if( cli_strncmp(zTable, "sqlite_", 7)==0 ){
     return 0;
   }else if( dataOnly ){
@@ -3237,7 +3297,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
   }else if( cli_strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
     char *zIns;
     if( !p->writableSchema ){
-      sqlite3_fputs("PRAGMA writable_schema=ON;\n", p->out);
+      cli_puts("PRAGMA writable_schema=ON;\n", p->out);
       p->writableSchema = 1;
     }
     zIns = sqlite3_mprintf(
@@ -3245,7 +3305,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
        "VALUES('table','%q','%q',0,'%q');",
        zTable, zTable, zSql);
     shell_check_oom(zIns);
-    sqlite3_fprintf(p->out, "%s\n", zIns);
+    cli_printf(p->out, "%s\n", zIns);
     sqlite3_free(zIns);
     return 0;
   }else{
@@ -3307,7 +3367,7 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azNotUsed){
     p->mode.spec.bColumnNames = QRF_No;
     rc = shell_exec(p, sSelect.zTxt, 0);
     if( (rc&0xff)==SQLITE_CORRUPT ){
-      sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out);
+      cli_puts("/****** CORRUPTION ERROR *******/\n", p->out);
       toggleSelectOrder(p->db);
       shell_exec(p, sSelect.zTxt, 0);
       toggleSelectOrder(p->db);
@@ -3337,9 +3397,9 @@ static int run_schema_dump_query(
   if( rc==SQLITE_CORRUPT ){
     char *zQ2;
     int len = strlen30(zQuery);
-    sqlite3_fputs("/****** CORRUPTION ERROR *******/\n", p->out);
+    cli_puts("/****** CORRUPTION ERROR *******/\n", p->out);
     if( zErr ){
-      sqlite3_fprintf(p->out, "/****** %s ******/\n", zErr);
+      cli_printf(p->out, "/****** %s ******/\n", zErr);
       sqlite3_free(zErr);
       zErr = 0;
     }
@@ -3348,7 +3408,7 @@ static int run_schema_dump_query(
     sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
     rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
     if( rc ){
-      sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
+      cli_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
     }else{
       rc = SQLITE_CORRUPT;
     }
@@ -3553,6 +3613,8 @@ static const char *(azHelp[]) = {
   ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
   "   If FILE begins with '|' then open it as a pipe.",
   "   If FILE is 'off' then output is disabled.",
+  "   If FILE is 'memory' then capture all output in an internal buffer.",
+  "   If FILE is omitted, restore output to the console.",
   "   Options:",
   "     --bom                 Prefix output with a UTF8 byte-order mark",
   "     -e                    Send output to the system text editor",
@@ -3723,10 +3785,10 @@ static int showHelp(FILE *out, const char *zPattern){
         show = 0;
       }else if( azHelp[i][0]==',' ){
         show = 1;
-        sqlite3_fprintf(out, ".%s\n", &azHelp[i][1]);
+        cli_printf(out, ".%s\n", &azHelp[i][1]);
         n++;
       }else if( show ){
-        sqlite3_fprintf(out, "%s\n", azHelp[i]);
+        cli_printf(out, "%s\n", azHelp[i]);
       }
     }
     return n;
@@ -3737,7 +3799,7 @@ static int showHelp(FILE *out, const char *zPattern){
   shell_check_oom(zPat);
   for(i=0; i<ArraySize(azHelp); i++){
     if( sqlite3_strglob(zPat, azHelp[i])==0 ){
-      sqlite3_fprintf(out, "%s\n", azHelp[i]);
+      cli_printf(out, "%s\n", azHelp[i]);
       j = i+1;
       n++;
     }
@@ -3748,7 +3810,7 @@ static int showHelp(FILE *out, const char *zPattern){
       /* when zPattern is a prefix of exactly one command, then include
       ** the details of that command, which should begin at offset j */
       while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
-        sqlite3_fprintf(out, "%s\n", azHelp[j]);
+        cli_printf(out, "%s\n", azHelp[j]);
         j++;
       }
     }
@@ -3766,10 +3828,10 @@ static int showHelp(FILE *out, const char *zPattern){
     }
     if( azHelp[i][0]=='.' ) j = i;
     if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
-      sqlite3_fprintf(out, "%s\n", azHelp[j]);
+      cli_printf(out, "%s\n", azHelp[j]);
       while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
         j++;
-        sqlite3_fprintf(out, "%s\n", azHelp[j]);
+        cli_printf(out, "%s\n", azHelp[j]);
       }
       i = j;
       n++;
@@ -3806,7 +3868,7 @@ static char *readFile(const char *zName, int *pnByte){
   if( in==0 ) return 0;
   rc = fseek(in, 0, SEEK_END);
   if( rc!=0 ){
-    sqlite3_fprintf(stderr,"Error: '%s' not seekable\n", zName);
+    cli_printf(stderr,"Error: '%s' not seekable\n", zName);
     fclose(in);
     return 0;
   }
@@ -3814,7 +3876,7 @@ static char *readFile(const char *zName, int *pnByte){
   rewind(in);
   pBuf = sqlite3_malloc64( nIn+1 );
   if( pBuf==0 ){
-    sqlite3_fputs("Error: out of memory\n", stderr);
+    cli_puts("Error: out of memory\n", stderr);
     fclose(in);
     return 0;
   }
@@ -3822,7 +3884,7 @@ static char *readFile(const char *zName, int *pnByte){
   fclose(in);
   if( nRead!=1 ){
     sqlite3_free(pBuf);
-    sqlite3_fprintf(stderr,"Error: cannot read '%s'\n", zName);
+    cli_printf(stderr,"Error: cannot read '%s'\n", zName);
     return 0;
   }
   pBuf[nIn] = 0;
@@ -3959,7 +4021,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
   if( zDbFilename ){
     in = sqlite3_fopen(zDbFilename, "r");
     if( in==0 ){
-      sqlite3_fprintf(stderr,"cannot open \"%s\" for reading\n", zDbFilename);
+      cli_printf(stderr,"cannot open \"%s\" for reading\n", zDbFilename);
       return 0;
     }
     nLine = 0;
@@ -3975,7 +4037,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
   if( rc!=2 ) goto readHexDb_error;
   if( n<0 ) goto readHexDb_error;
   if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
-    sqlite3_fputs("invalid pagesize\n", stderr);
+    cli_puts("invalid pagesize\n", stderr);
     goto readHexDb_error;
   }
   sz = ((i64)n+pgsz-1)&~(pgsz-1); /* Round up to nearest multiple of pgsz */
@@ -3986,7 +4048,7 @@ static unsigned char *readHexDb(ShellState *p, int *pnData){
     int j = 0;                    /* Page number from "| page" line */
     int k = 0;                    /* Offset from "| page" line */
     if( nLine>=2000000000 ){
-      sqlite3_fprintf(stderr, "input too big\n");
+      cli_printf(stderr, "input too big\n");
       goto readHexDb_error;
     }
     rc = sscanf(zLine, "| page %d offset %d", &j, &k);
@@ -4027,7 +4089,7 @@ readHexDb_error:
     p->lineno = nLine;
   }
   sqlite3_free(a);
-  sqlite3_fprintf(stderr,"Error on line %lld of --hexdb input\n", nLine);
+  cli_printf(stderr,"Error on line %lld of --hexdb input\n", nLine);
   return 0;
 }
 #endif /* SQLITE_OMIT_DESERIALIZE */
@@ -4132,19 +4194,19 @@ static void open_db(ShellState *p, int openFlags){
       }
     }
     if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
-      sqlite3_fprintf(stderr,"Error: unable to open database \"%s\": %s\n",
+      cli_printf(stderr,"Error: unable to open database \"%s\": %s\n",
             zDbFilename, sqlite3_errmsg(p->db));
       if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
-        exit(1);
+        cli_exit(1);
       }
       sqlite3_close(p->db);
       sqlite3_open(":memory:", &p->db);
       if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
-        sqlite3_fputs("Also: unable to open substitute in-memory database.\n",
+        cli_puts("Also: unable to open substitute in-memory database.\n",
                       stderr);
-        exit(1);
+        cli_exit(1);
       }else{
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
               "Notice: using substitute in-memory database instead of \"%s\"\n",
               zDbFilename);
       }
@@ -4258,7 +4320,7 @@ static void open_db(ShellState *p, int openFlags){
                    SQLITE_DESERIALIZE_RESIZEABLE |
                    SQLITE_DESERIALIZE_FREEONCLOSE);
       if( rc ){
-        sqlite3_fprintf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc);
+        cli_printf(stderr,"Error: sqlite3_deserialize() returns %d\n", rc);
       }
       if( p->szMax>0 ){
         sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
@@ -4284,7 +4346,7 @@ static void open_db(ShellState *p, int openFlags){
 void close_db(sqlite3 *db){
   int rc = sqlite3_close(db);
   if( rc ){
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
         "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db));
   }
 }
@@ -4457,7 +4519,7 @@ static int booleanValue(const char *zArg){
   if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
     return 0;
   }
-  sqlite3_fprintf(stderr,
+  cli_printf(stderr,
        "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n", zArg);
   return 0;
 }
@@ -4496,7 +4558,7 @@ static FILE *output_file_open(const char *zFile){
   }else{
     f = sqlite3_fopen(zFile, "w");
     if( f==0 ){
-      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile);
+      cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
     }
   }
   return f;
@@ -4549,12 +4611,12 @@ static int sql_trace_callback(
   switch( mType ){
     case SQLITE_TRACE_ROW:
     case SQLITE_TRACE_STMT: {
-      sqlite3_fprintf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
+      cli_printf(p->traceOut, "%.*s;\n", (int)nSql, zSql);
       break;
     }
     case SQLITE_TRACE_PROFILE: {
       sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
-      sqlite3_fprintf(p->traceOut,
+      cli_printf(p->traceOut,
                       "%.*s; -- %lld ns\n", (int)nSql, zSql, nNanosec);
       break;
     }
@@ -4662,11 +4724,11 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
         break;
       }
       if( pc==cQuote && c!='\r' ){
-        sqlite3_fprintf(stderr,"%s:%d: unescaped %c character\n", 
+        cli_printf(stderr,"%s:%d: unescaped %c character\n", 
                         p->zFile, p->nLine, cQuote);
       }
       if( c==EOF ){
-        sqlite3_fprintf(stderr,"%s:%d: unterminated %c-quoted field\n",
+        cli_printf(stderr,"%s:%d: unterminated %c-quoted field\n",
               p->zFile, startLine, cQuote);
         p->cTerm = c;
         break;
@@ -4765,7 +4827,7 @@ static void tryToCloneData(
   shell_check_oom(zQuery);
   rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
   if( rc ){
-    sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n",
+    cli_printf(stderr,"Error %d: %s on [%s]\n",
           sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery);
     goto end_data_xfer;
   }
@@ -4782,7 +4844,7 @@ static void tryToCloneData(
   memcpy(zInsert+i, ");", 3);
   rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0);
   if( rc ){
-    sqlite3_fprintf(stderr,"Error %d: %s on [%s]\n",
+    cli_printf(stderr,"Error %d: %s on [%s]\n",
           sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb), zInsert);
     goto end_data_xfer;
   }
@@ -4818,7 +4880,7 @@ static void tryToCloneData(
       } /* End for */
       rc = sqlite3_step(pInsert);
       if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
-        sqlite3_fprintf(stderr,"Error %d: %s\n",
+        cli_printf(stderr,"Error %d: %s\n",
               sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb));
       }
       sqlite3_reset(pInsert);
@@ -4836,7 +4898,7 @@ static void tryToCloneData(
     shell_check_oom(zQuery);
     rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
     if( rc ){
-      sqlite3_fprintf(stderr,"Warning: cannot step \"%s\" backwards", zTable);
+      cli_printf(stderr,"Warning: cannot step \"%s\" backwards", zTable);
       break;
     }
   } /* End for(k=0...) */
@@ -4873,7 +4935,7 @@ static void tryToCloneSchema(
   shell_check_oom(zQuery);
   rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
   if( rc ){
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
           "Error: (%d) %s on [%s]\n", sqlite3_extended_errcode(p->db),
           sqlite3_errmsg(p->db), zQuery);
     goto end_schema_xfer;
@@ -4883,10 +4945,10 @@ static void tryToCloneSchema(
     zSql = sqlite3_column_text(pQuery, 1);
     if( zName==0 || zSql==0 ) continue;
     if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
-      sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout);
+      cli_printf(stdout, "%s... ", zName); fflush(stdout);
       sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
       if( zErrMsg ){
-        sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
+        cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
         sqlite3_free(zErrMsg);
         zErrMsg = 0;
       }
@@ -4904,7 +4966,7 @@ static void tryToCloneSchema(
     shell_check_oom(zQuery);
     rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
     if( rc ){
-      sqlite3_fprintf(stderr,"Error: (%d) %s on [%s]\n",
+      cli_printf(stderr,"Error: (%d) %s on [%s]\n",
             sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zQuery);
       goto end_schema_xfer;
     }
@@ -4913,10 +4975,10 @@ static void tryToCloneSchema(
       zSql = sqlite3_column_text(pQuery, 1);
       if( zName==0 || zSql==0 ) continue;
       if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
-      sqlite3_fprintf(stdout, "%s... ", zName); fflush(stdout);
+      cli_printf(stdout, "%s... ", zName); fflush(stdout);
       sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
       if( zErrMsg ){
-        sqlite3_fprintf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
+        cli_printf(stderr,"Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
         sqlite3_free(zErrMsg);
         zErrMsg = 0;
       }
@@ -4940,12 +5002,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){
   int rc;
   sqlite3 *newDb = 0;
   if( access(zNewDb,0)==0 ){
-    sqlite3_fprintf(stderr,"File \"%s\" already exists.\n", zNewDb);
+    cli_printf(stderr,"File \"%s\" already exists.\n", zNewDb);
     return;
   }
   rc = sqlite3_open(zNewDb, &newDb);
   if( rc ){
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
         "Cannot create output database: %s\n", sqlite3_errmsg(newDb));
   }else{
     sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0);
@@ -4964,12 +5026,12 @@ static void tryToClone(ShellState *p, const char *zNewDb){
 */
 static void output_redir(ShellState *p, FILE *pfNew){
   if( p->out != stdout ){
-    sqlite3_fputs("Output already redirected.\n", stderr);
+    cli_puts("Output already redirected.\n", stderr);
   }else{
     p->out = pfNew;
     setCrlfMode(p);
     if( p->mode.eMode==MODE_Www ){
-      sqlite3_fputs(
+      cli_puts(
         "<!DOCTYPE html>\n"
         "<HTML><BODY><PRE>\n",
         p->out
@@ -4992,7 +5054,7 @@ static void output_reset(ShellState *p){
 #endif
   }else{
     if( p->mode.eMode==MODE_Www ){
-      sqlite3_fputs("</PRE></BODY></HTML>\n", p->out);
+      cli_puts("</PRE></BODY></HTML>\n", p->out);
     }
     output_file_close(p->out);
 #ifndef SQLITE_NOHAVE_SYSTEM
@@ -5008,7 +5070,7 @@ static void output_reset(ShellState *p){
       char *zCmd;
       zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile);
       if( system(zCmd) ){
-        sqlite3_fprintf(stderr,"Failed: [%s]\n", zCmd);
+        cli_printf(stderr,"Failed: [%s]\n", zCmd);
       }else{
         /* Give the start/open/xdg-open command some time to get
         ** going before we continue, and potential delete the
@@ -5024,6 +5086,10 @@ static void output_reset(ShellState *p){
   p->outfile[0] = 0;
   p->out = stdout;
   setCrlfMode(p);
+  if( cli_output_capture ){
+    sqlite3_free(sqlite3_str_finish(cli_output_capture));
+    cli_output_capture = 0;
+  }
 }
 #else
 # define output_redir(SS,pfO)
@@ -5108,7 +5174,7 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
              "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
              -1, &pStmt, 0);
   if( rc ){
-    sqlite3_fprintf(stderr,"error: %s\n", sqlite3_errmsg(p->db));
+    cli_printf(stderr,"error: %s\n", sqlite3_errmsg(p->db));
     sqlite3_finalize(pStmt);
     return 1;
   }
@@ -5121,28 +5187,28 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
     memcpy(aHdr, pb, 100);
     sqlite3_finalize(pStmt);
   }else{
-    sqlite3_fputs("unable to read database header\n", stderr);
+    cli_puts("unable to read database header\n", stderr);
     sqlite3_finalize(pStmt);
     return 1;
   }
   i = get2byteInt(aHdr+16);
   if( i==1 ) i = 65536;
-  sqlite3_fprintf(p->out, "%-20s %d\n", "database page size:", i);
-  sqlite3_fprintf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
-  sqlite3_fprintf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
-  sqlite3_fprintf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
+  cli_printf(p->out, "%-20s %d\n", "database page size:", i);
+  cli_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
+  cli_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
+  cli_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
   for(i=0; i<ArraySize(aField); i++){
     int ofst = aField[i].ofst;
     unsigned int val = get4byteInt(aHdr + ofst);
-    sqlite3_fprintf(p->out, "%-20s %u", aField[i].zName, val);
+    cli_printf(p->out, "%-20s %u", aField[i].zName, val);
     switch( ofst ){
       case 56: {
-        if( val==1 ) sqlite3_fputs(" (utf8)", p->out);
-        if( val==2 ) sqlite3_fputs(" (utf16le)", p->out);
-        if( val==3 ) sqlite3_fputs(" (utf16be)", p->out);
+        if( val==1 ) cli_puts(" (utf8)", p->out);
+        if( val==2 ) cli_puts(" (utf16le)", p->out);
+        if( val==3 ) cli_puts(" (utf16be)", p->out);
       }
     }
-    sqlite3_fputs("\n", p->out);
+    cli_puts("\n", p->out);
   }
   if( zDb==0 ){
     zSchemaTab = sqlite3_mprintf("main.sqlite_schema");
@@ -5153,11 +5219,11 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
   }
   for(i=0; i<ArraySize(aQuery); i++){
     int val = db_int(p->db, aQuery[i].zSql, zSchemaTab);
-    sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
+    cli_printf(p->out, "%-20s %d\n", aQuery[i].zName, val);
   }
   sqlite3_free(zSchemaTab);
   sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
-  sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion);
+  cli_printf(p->out, "%-20s %u\n", "data version", iDataVersion);
   return 0;
 }
 #endif /* SQLITE_SHELL_HAVE_RECOVER */
@@ -5217,7 +5283,7 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){
   }
   zName = strdup(zTail);
   shell_check_oom(zName);
-  sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n",
+  cli_printf(p->out, "| size %lld pagesize %d filename %s\n",
                   nPage*pgSz, pgSz, zName);
   sqlite3_finalize(pStmt);
   pStmt = 0;
@@ -5233,27 +5299,27 @@ static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){
       for(j=0; j<16 && aLine[j]==0; j++){}
       if( j==16 ) continue;
       if( !seenPageLabel ){
-        sqlite3_fprintf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz);
+        cli_printf(p->out, "| page %lld offset %lld\n",pgno,(pgno-1)*pgSz);
         seenPageLabel = 1;
       }
-      sqlite3_fprintf(p->out, "|  %5d:", i);
-      for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
-      sqlite3_fprintf(p->out, "   ");
+      cli_printf(p->out, "|  %5d:", i);
+      for(j=0; j<16; j++) cli_printf(p->out, " %02x", aLine[j]);
+      cli_printf(p->out, "   ");
       for(j=0; j<16; j++){
         unsigned char c = (unsigned char)aLine[j];
-        sqlite3_fprintf(p->out, "%c", bShow[c]);
+        cli_printf(p->out, "%c", bShow[c]);
       }
-      sqlite3_fprintf(p->out, "\n");
+      cli_printf(p->out, "\n");
     }
   }
   sqlite3_finalize(pStmt);
-  sqlite3_fprintf(p->out, "| end %s\n", zName);
+  cli_printf(p->out, "| end %s\n", zName);
   free(zName);
   return 0;
 
 dbtotxt_error:
   if( rc ){
-    sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db));
+    cli_printf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db));
   }
   sqlite3_finalize(pStmt);
   free(zName);
@@ -5264,7 +5330,7 @@ dbtotxt_error:
 ** Print the given string as an error message.
 */
 static void shellEmitError(const char *zErr){
-  sqlite3_fprintf(stderr,"Error: %s\n", zErr);
+  cli_printf(stderr,"Error: %s\n", zErr);
 }
 /*
 ** Print the current sqlite3_errmsg() value to stderr and return 1.
@@ -5627,7 +5693,7 @@ static int lintFkeyIndexes(
       zIndent = "    ";
     }
     else{
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
            "Usage: %s %s ?-verbose? ?-groupbyparent?\n", azArg[0], azArg[1]);
       return SQLITE_ERROR;
     }
@@ -5672,22 +5738,22 @@ static int lintFkeyIndexes(
       if( rc!=SQLITE_OK ) break;
 
       if( res<0 ){
-        sqlite3_fputs("Error: internal error", stderr);
+        cli_puts("Error: internal error", stderr);
         break;
       }else{
         if( bGroupByParent
         && (bVerbose || res==0)
         && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
         ){
-          sqlite3_fprintf(out, "-- Parent table %s\n", zParent);
+          cli_printf(out, "-- Parent table %s\n", zParent);
           sqlite3_free(zPrev);
           zPrev = sqlite3_mprintf("%s", zParent);
         }
 
         if( res==0 ){
-          sqlite3_fprintf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
+          cli_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
         }else if( bVerbose ){
-          sqlite3_fprintf(out,
+          cli_printf(out,
                 "%s/* no extra indexes required for %s -> %s */\n",
                 zIndent, zFrom, zTarget
           );
@@ -5697,16 +5763,16 @@ static int lintFkeyIndexes(
     sqlite3_free(zPrev);
 
     if( rc!=SQLITE_OK ){
-      sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db));
+      cli_printf(stderr,"%s\n", sqlite3_errmsg(db));
     }
 
     rc2 = sqlite3_finalize(pSql);
     if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
       rc = rc2;
-      sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db));
+      cli_printf(stderr,"%s\n", sqlite3_errmsg(db));
     }
   }else{
-    sqlite3_fprintf(stderr,"%s\n", sqlite3_errmsg(db));
+    cli_printf(stderr,"%s\n", sqlite3_errmsg(db));
   }
 
   return rc;
@@ -5726,9 +5792,9 @@ static int lintDotCommand(
   return lintFkeyIndexes(pState, azArg, nArg);
 
  usage:
-  sqlite3_fprintf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]);
-  sqlite3_fprintf(stderr, "Where sub-commands are:\n");
-  sqlite3_fprintf(stderr, "    fkey-indexes\n");
+  cli_printf(stderr,"Usage %s sub-command ?switches...?\n", azArg[0]);
+  cli_printf(stderr, "Where sub-commands are:\n");
+  cli_printf(stderr, "    fkey-indexes\n");
   return SQLITE_ERROR;
 }
 
@@ -5742,7 +5808,7 @@ static void shellPrepare(
   if( *pRc==SQLITE_OK ){
     int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
     if( rc!=SQLITE_OK ){
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
          "sql error: %s (%d)\n", sqlite3_errmsg(db), sqlite3_errcode(db));
       *pRc = rc;
     }
@@ -5787,7 +5853,7 @@ static void shellFinalize(
     int rc = sqlite3_finalize(pStmt);
     if( *pRc==SQLITE_OK ){
       if( rc!=SQLITE_OK ){
-        sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db));
+        cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db));
       }
       *pRc = rc;
     }
@@ -5809,7 +5875,7 @@ void shellReset(
   if( *pRc==SQLITE_OK ){
     if( rc!=SQLITE_OK ){
       sqlite3 *db = sqlite3_db_handle(pStmt);
-      sqlite3_fprintf(stderr,"SQL error: %s\n", sqlite3_errmsg(db));
+      cli_printf(stderr,"SQL error: %s\n", sqlite3_errmsg(db));
     }
     *pRc = rc;
   }
@@ -5862,9 +5928,9 @@ static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
   va_end(ap);
   shellEmitError(z);
   if( pAr->fromCmdLine ){
-    sqlite3_fputs("Use \"-A\" for more help\n", stderr);
+    cli_puts("Use \"-A\" for more help\n", stderr);
   }else{
-    sqlite3_fputs("Use \".archive --help\" for more help\n", stderr);
+    cli_puts("Use \".archive --help\" for more help\n", stderr);
   }
   sqlite3_free(z);
   return SQLITE_ERROR;
@@ -5964,7 +6030,7 @@ static int arParseCommand(
   struct ArSwitch *pEnd = &aSwitch[nSwitch];
 
   if( nArg<=1 ){
-    sqlite3_fprintf(stderr, "Wrong number of arguments.  Usage:\n");
+    cli_printf(stderr, "Wrong number of arguments.  Usage:\n");
     return arUsage(stderr);
   }else{
     char *z = azArg[1];
@@ -6070,7 +6136,7 @@ static int arParseCommand(
     }
   }
   if( pAr->eCmd==0 ){
-    sqlite3_fprintf(stderr, "Required argument missing.  Usage:\n");
+    cli_printf(stderr, "Required argument missing.  Usage:\n");
     return arUsage(stderr);
   }
   return SQLITE_OK;
@@ -6113,7 +6179,7 @@ static int arCheckEntries(ArCommand *pAr){
       }
       shellReset(&rc, pTest);
       if( rc==SQLITE_OK && bOk==0 ){
-        sqlite3_fprintf(stderr,"not found in archive: %s\n", z);
+        cli_printf(stderr,"not found in archive: %s\n", z);
         rc = SQLITE_ERROR;
       }
     }
@@ -6196,15 +6262,15 @@ static int arListCommand(ArCommand *pAr){
   shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
                      pAr->zSrcTable, zWhere);
   if( pAr->bDryRun ){
-    sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql));
+    cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
   }else{
     while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
       if( pAr->bVerbose ){
-        sqlite3_fprintf(pAr->out, "%s % 10d  %s  %s\n",
+        cli_printf(pAr->out, "%s % 10d  %s  %s\n",
               sqlite3_column_text(pSql, 0), sqlite3_column_int(pSql, 1),
               sqlite3_column_text(pSql, 2),sqlite3_column_text(pSql, 3));
       }else{
-        sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
+        cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
       }
     }
   }
@@ -6231,7 +6297,7 @@ static int arRemoveCommand(ArCommand *pAr){
     zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
                            pAr->zSrcTable, zWhere);
     if( pAr->bDryRun ){
-      sqlite3_fprintf(pAr->out, "%s\n", zSql);
+      cli_printf(pAr->out, "%s\n", zSql);
     }else{
       char *zErr = 0;
       rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
@@ -6244,7 +6310,7 @@ static int arRemoveCommand(ArCommand *pAr){
         }
       }
       if( zErr ){
-        sqlite3_fprintf(stdout, "ERROR: %s\n", zErr); /* stdout? */
+        cli_printf(stdout, "ERROR: %s\n", zErr); /* stdout? */
         sqlite3_free(zErr);
       }
     }
@@ -6308,11 +6374,11 @@ static int arExtractCommand(ArCommand *pAr){
       j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
       sqlite3_bind_int(pSql, j, i);
       if( pAr->bDryRun ){
-        sqlite3_fprintf(pAr->out, "%s\n", sqlite3_sql(pSql));
+        cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql));
       }else{
         while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
           if( i==0 && pAr->bVerbose ){
-            sqlite3_fprintf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
+            cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0));
           }
         }
       }
@@ -6332,13 +6398,13 @@ static int arExtractCommand(ArCommand *pAr){
 static int arExecSql(ArCommand *pAr, const char *zSql){
   int rc;
   if( pAr->bDryRun ){
-    sqlite3_fprintf(pAr->out, "%s\n", zSql);
+    cli_printf(pAr->out, "%s\n", zSql);
     rc = SQLITE_OK;
   }else{
     char *zErr = 0;
     rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
     if( zErr ){
-      sqlite3_fprintf(stdout, "ERROR: %s\n", zErr);
+      cli_printf(stdout, "ERROR: %s\n", zErr);
       sqlite3_free(zErr);
     }
   }
@@ -6514,13 +6580,13 @@ static int arDotCommand(
       }
       cmd.db = 0;
       if( cmd.bDryRun ){
-        sqlite3_fprintf(cmd.out, "-- open database '%s'%s\n", cmd.zFile,
+        cli_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile,
               eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
       }
       rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
              eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
       if( rc!=SQLITE_OK ){
-        sqlite3_fprintf(stderr, "cannot open file: %s (%s)\n",
+        cli_printf(stderr, "cannot open file: %s (%s)\n",
                         cmd.zFile, sqlite3_errmsg(cmd.db));
         goto end_ar_command;
       }
@@ -6534,7 +6600,7 @@ static int arDotCommand(
       if( cmd.eCmd!=AR_CMD_CREATE
        && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
       ){
-        sqlite3_fprintf(stderr, "database does not contain an 'sqlar' table\n");
+        cli_printf(stderr, "database does not contain an 'sqlar' table\n");
         rc = SQLITE_ERROR;
         goto end_ar_command;
       }
@@ -6592,7 +6658,7 @@ end_ar_command:
 */
 static int recoverSqlCb(void *pCtx, const char *zSql){
   ShellState *pState = (ShellState*)pCtx;
-  sqlite3_fprintf(pState->out, "%s;\n", zSql);
+  cli_printf(pState->out, "%s;\n", zSql);
   return SQLITE_OK;
 }
 
@@ -6635,7 +6701,7 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
       bRowids = 0;
     }
     else{
-      sqlite3_fprintf(stderr,"unexpected option: %s\n", azArg[i]);
+      cli_printf(stderr,"unexpected option: %s\n", azArg[i]);
       showHelp(pState->out, azArg[0]);
       return 1;
     }
@@ -6650,12 +6716,12 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
   sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
   sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
 
-  sqlite3_fprintf(pState->out, ".dbconfig defensive off\n");
+  cli_printf(pState->out, ".dbconfig defensive off\n");
   sqlite3_recover_run(p);
   if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
     const char *zErr = sqlite3_recover_errmsg(p);
     int errCode = sqlite3_recover_errcode(p);
-    sqlite3_fprintf(stderr,"sql error: %s (%d)\n", zErr, errCode);
+    cli_printf(stderr,"sql error: %s (%d)\n", zErr, errCode);
   }
   rc = sqlite3_recover_finish(p);
   return rc;
@@ -6677,7 +6743,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){
     while( SQLITE_OK==sqlite3_intck_step(p) ){
       const char *zMsg = sqlite3_intck_message(p);
       if( zMsg ){
-        sqlite3_fprintf(pState->out, "%s\n", zMsg);
+        cli_printf(pState->out, "%s\n", zMsg);
         nError++;
       }
       nStep++;
@@ -6687,11 +6753,11 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){
     }
     rc = sqlite3_intck_error(p, &zErr);
     if( zErr ){
-      sqlite3_fprintf(stderr,"%s\n", zErr);
+      cli_printf(stderr,"%s\n", zErr);
     }
     sqlite3_intck_close(p);
 
-    sqlite3_fprintf(pState->out, "%lld steps, %lld errors\n", nStep, nError);
+    cli_printf(pState->out, "%lld steps, %lld errors\n", nStep, nError);
   }
 
   return rc;
@@ -6714,7 +6780,7 @@ static int intckDatabaseCmd(ShellState *pState, i64 nStepPerUnlock){
 #define rc_err_oom_die(rc) \
   if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
   else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
-    sqlite3_fprintf(stderr,"E:%d\n",rc), assert(0)
+    cli_printf(stderr,"E:%d\n",rc), assert(0)
 #else
 static void rc_err_oom_die(int rc){
   if( rc==SQLITE_NOMEM ) shell_check_oom(0);
@@ -6931,7 +6997,7 @@ static int outputDumpWarning(ShellState *p, const char *zLike){
     "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true"
   );
   if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-    sqlite3_fputs("/* WARNING: "
+    cli_puts("/* WARNING: "
           "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n",
           p->out
     );
@@ -6964,13 +7030,13 @@ static int faultsim_callback(int iArg){
   if( faultsim_state.iCnt ){
     if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--;
     if( faultsim_state.eVerbose>=2 ){
-      sqlite3_fprintf(stdout, 
+      cli_printf(stdout, 
          "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt);
     }
     return SQLITE_OK;
   }
   if( faultsim_state.eVerbose>=1 ){
-    sqlite3_fprintf(stdout, 
+    cli_printf(stdout, 
          "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr);
   }
   faultsim_state.iCnt = faultsim_state.iInterval;
@@ -6982,7 +7048,13 @@ static int faultsim_callback(int iArg){
 }
 
 /*
-** COMMAND:  .mode
+** DOT-COMMAND:  .mode
+**
+** USAGE:  .mode [OPTIONS] [MODE]
+**
+** Change the output mode to MODE.  And/or apply OPTIONS to the
+** output mode.  If no arguments, show the current output mode and
+** relevant options.
 */
 static int dotCmdMode(ShellState *p){
   int nArg = p->dot.nArg;
@@ -7041,12 +7113,12 @@ static int dotCmdMode(ShellState *p){
         }
       }
       if( k>=ArraySize(qrfEscNames) ){
-        sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\""
+        cli_printf(stderr, "unknown control character escape mode \"%s\""
                                 " - choices:", zEsc);
         for(k=0; k<ArraySize(qrfEscNames); k++){
-          sqlite3_fprintf(stderr, " %s", qrfEscNames[k]);
+          cli_printf(stderr, " %s", qrfEscNames[k]);
         }
-        sqlite3_fprintf(stderr, "\n");
+        cli_printf(stderr, "\n");
         return 1;
       }
     }else if( zMode==0 ){
@@ -7063,7 +7135,7 @@ static int dotCmdMode(ShellState *p){
     }else if( zTabname==0 ){
       zTabname = z;
     }else if( z[0]=='-' ){
-      sqlite3_fprintf(stderr,"unknown option: %s\n", z);
+      cli_printf(stderr,"unknown option: %s\n", z);
       eputz("options:\n"
             "  --escape MODE\n"
             "  --noquote\n"
@@ -7073,7 +7145,7 @@ static int dotCmdMode(ShellState *p){
             "  --ww\n");
       return 1;
     }else{
-      sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z);
+      cli_printf(stderr,"extra argument: \"%s\"\n", z);
       return 1;
     }
   }
@@ -7083,22 +7155,22 @@ static int dotCmdMode(ShellState *p){
      || p->mode.eMode==MODE_Table
      || p->mode.eMode==MODE_Markdown
     ){
-      sqlite3_fprintf(p->out, "current output mode: %s",
+      cli_printf(p->out, "current output mode: %s",
                       modeDescr[p->mode.eMode]);
       if( p->mode.spec.mxColWidth ){
-        sqlite3_fprintf(p->out, " --wrap %d",
+        cli_printf(p->out, " --wrap %d",
            p->mode.spec.mxColWidth);
       }else{
-        sqlite3_fprintf(p->out, " --wrap off");
+        cli_printf(p->out, " --wrap off");
       }
-      sqlite3_fprintf(p->out, " --wordwrap %s --%squote --escape %s\n",
+      cli_printf(p->out, " --wordwrap %s --%squote --escape %s\n",
             p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off",
             p->mode.spec.eText==QRF_TEXT_Sql ? "" : "no",
             qrfEscNames[p->mode.spec.eEsc==QRF_ESC_Auto?
                 QRF_ESC_Ascii:p->mode.spec.eEsc]
       );
     }else{
-      sqlite3_fprintf(p->out,
+      cli_printf(p->out,
             "current output mode: %s --escape %s\n",
             modeDescr[p->mode.eMode],
             qrfEscNames[p->mode.spec.eEsc==QRF_ESC_Auto?
@@ -7165,6 +7237,311 @@ static int dotCmdMode(ShellState *p){
   return 0;
 }
 
+/*
+** DOT-COMMAND: .output [OPTIONS] [FILE]
+** ONELINER:     Redirect output
+**
+** Begin redirecting output to FILE.  Or if FILE is omitted, revert
+** to sending output to the console.  If FILE begins with "|" then
+** the remainder of file is taken as a pipe and output is directed
+** into that pipe.  If FILE is "memory" then output is captured in an
+** internal memory buffer.  If FILE is "off" then output is redirected
+** into /dev/null or the equivalent.
+**
+** Options:
+**
+**   --bom             Prepend a byte-order mark to the output
+**
+**   -e                Accumulate output in a temporary text file then
+**                     launch a text editor when the redirection ends.
+**
+**   --glob GLOB       Raise an error if the memory buffer does not match
+**                     the GLOB pattern.
+**
+**   --keep            Continue using the same "memory" buffer.  Do not
+**                     reset it or delete it.  Useful in combination with
+**                     --glob, --not-glob, and/or --verify.
+**
+**   ---notglob GLOB   Raise an error if the memory buffer does not match
+**                     the GLOB pattern.
+**
+**   --plain           Use plain text rather than HTML tables with -w
+**
+**   --show            Write the memory buffer to the screen, for debugging.
+**
+**   --verify ENDMARK  Read subsequent lines of text until the first line
+**                     that matches ENDMARK.  Discard the ENDMARK.  Compare
+**                     the text against the accumulated output in memory and
+**                     raise an error if there are any differences.
+**
+**   -w                Show the output in a web browser.  Output is
+**                     written into a temporary HTML file until the
+**                     redirect ends, then the web browser is launched.
+**                     Query results  are shown as HTML tables, unless
+**                     the --plain is used too.
+**
+**   -x                Show the output in a spreadsheet.  Output is
+**                     written to a temp file as CSV then the spreadsheet
+**                     is launched when
+**
+** DOT-COMMAND: .once [OPTIONS] FILE ...
+** ONELINER:     Redirect output for the next SQL statement or dot-command
+**
+** Write the output for the next line of SQL or the next dot-command into
+** FILE.  If FILE begins with "|" then it is a program into which output
+** is written. The FILE argument should be omitted if one of the -e, -w,
+** or -x options is used.
+**
+** Options:
+**
+**   -e                Capture output into a temporary file then bring up
+**                     a text editor on that temporary file.
+**
+**   --plain           Use plain text rather than HTML tables with -w
+**
+**   -w                Capture output into an HTML file then bring up that
+**                     file in a web browser
+**
+**   -x                Show the output in a spreadsheet.  Output is
+**                     written to a temp file as CSV then the spreadsheet
+**                     is launched when
+**
+** DOT-COMMAND: .excel
+** ONELINER:    Display results of the next SQL statement in a spreadsheet
+**
+** Shorthand for ".once -x"
+**
+** DOT-COMMAND: .www [--plain]
+** ONELINER:    Display result of the next SQL statement in a web browser
+**
+** Shorthand for ".once -w" or ".once --plain -w"
+*/
+static int dotCmdOutput(ShellState *p){
+  int nArg = p->dot.nArg;        /* Number of arguments */
+  char **azArg = p->dot.azArg;   /* Text of the arguments */
+  char *zFile = 0;               /* The FILE argument */
+  int i;                  /* Loop counter */
+  int eMode = 0;          /* 0: .outout/.once, 'x'=.excel, 'w'=.www */
+  int bOnce = 0;          /* 0: .output, 1: .once, 2: .excel/.www */
+  int bPlain = 0;         /* --plain option */
+  int bKeep = 0;          /* --keep option */
+  char *zCheck = 0;       /* Argument to --glob, --notglob, --verify */
+  int eCheck = 0;         /* 1: --glob,  2: --notglob,  3: --verify */
+  static const char *zBomUtf8 = "\357\273\277";
+  const char *zBom = 0;
+  char c = azArg[0][0];
+  int n = strlen30(azArg[0]);
+
+  failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
+  if( c=='e' ){
+    eMode = 'x';
+    bOnce = 2;
+  }else if( c=='w' ){
+    eMode = 'w';
+    bOnce = 2;
+  }else if( n>=2 && cli_strncmp(azArg[0],"once",n)==0 ){
+    bOnce = 1;
+  }
+  for(i=1; i<nArg; i++){
+    char *z = azArg[i];
+    if( z[0]=='-' ){
+      if( z[1]=='-' ) z++;
+      if( cli_strcmp(z,"-bom")==0 ){
+        zBom = zBomUtf8;
+      }else if( cli_strcmp(z,"-plain")==0 ){
+        bPlain = 1;
+      }else if( c=='o' && z[0]=='1' && z[1]!=0 && z[2]==0
+             && (z[1]=='x' || z[1]=='e' || z[1]=='w') ){
+        if( bKeep || eMode || eCheck ){
+          dotCmdError(p, i, "incompatible with prior options",0);
+          goto dotCmdOutput_error;
+        }
+        eMode = z[1];
+      }else if( cli_strcmp(z,"-keep")==0 ){
+        bKeep = 1;
+      }else if( cli_strcmp(z,"-show")==0 ){
+        if( cli_output_capture ){
+          sqlite3_fprintf(stdout, "%s", sqlite3_str_value(cli_output_capture));
+        }
+        bKeep = 1;
+      }else if( cli_strcmp(z,"-glob")==0
+             || cli_strcmp(z,"-notglob")==0
+             || cli_strcmp(z,"-verify")==0
+      ){
+        if( eCheck || eMode ){
+          dotCmdError(p, i, "incompatible with prior options",0);
+          goto dotCmdOutput_error;
+        }
+        if( i+1>=nArg ){
+          dotCmdError(p, i, "missing argument", 0);
+          goto dotCmdOutput_error;
+        }
+        zCheck = azArg[++i];
+        eCheck = z[1]=='g' ? 1 : z[1]=='n' ? 2 : 3;
+      }else{
+        dotCmdError(p, i, "unknown option", 0);
+        sqlite3_free(zFile);
+        return 1;
+      }
+    }else if( zFile==0 && eMode==0 ){
+      if( bKeep || eCheck ){
+        dotCmdError(p, i, "incompatible with prior options",0);
+        goto dotCmdOutput_error;
+      }
+      if( cli_strcmp(z, "memory")==0 && bOnce ){
+        dotCmdError(p, 0, "cannot redirect to \"memory\"", 0);
+        goto dotCmdOutput_error;
+      }
+      if( cli_strcmp(z, "off")==0 ){
+#ifdef _WIN32
+        zFile = sqlite3_mprintf("nul");
+#else
+        zFile = sqlite3_mprintf("/dev/null");
+#endif
+      }else{
+        zFile = sqlite3_mprintf("%s", z);
+      }
+      if( zFile && zFile[0]=='|' ){
+        while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
+        break;
+      }
+    }else{
+      dotCmdError(p, i, "surplus argument", 0);
+      sqlite3_free(zFile);
+      return 1;
+    }
+  }
+  if( zFile==0 && !bKeep ){
+    zFile = sqlite3_mprintf("stdout");
+    shell_check_oom(zFile);
+  }
+  if( bOnce ){
+    p->outCount = 2;
+  }else{
+    p->outCount = 0;
+  }
+  if( eCheck ){
+    char *zTest;
+    if( cli_output_capture ){
+      zTest = sqlite3_str_value(cli_output_capture);
+    }else{
+      zTest = "";
+    }
+    p->nTestRun++;
+    if( eCheck==3 ){
+      int nCheck = strlen30(zCheck);
+      sqlite3_str *pPattern = sqlite3_str_new(p->db);
+      char *zPattern;
+      char zLine[2000];
+      while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){
+        if( strchr(zLine,'\n') ) p->lineno++;
+        if( cli_strncmp(zCheck,zLine,nCheck)==0 ) break;
+        sqlite3_str_appendall(pPattern, zLine);
+      }
+      zPattern = sqlite3_str_finish(pPattern);
+      if( cli_strcmp(zPattern,zTest)!=0 ){
+        sqlite3_fprintf(stderr,
+            "%s:%lld: --verify does matches prior output\n",
+          p->zInFile, p->lineno);
+        p->nTestErr++;
+      }
+      sqlite3_free(zPattern);
+    }else{
+      char *zGlob = sqlite3_mprintf("*%s*", zCheck);
+      if( eCheck==1 && sqlite3_strglob(zGlob, zTest)!=0 ){
+        sqlite3_fprintf(stderr,
+            "%s:%lld: --glob \"%s\" does not match prior output\n",
+            p->zInFile, p->lineno, zCheck);
+        p->nTestErr++;       
+      }else if( eCheck==2 && sqlite3_strglob(zGlob, zTest)==0 ){
+        sqlite3_fprintf(stderr,
+            "%s:%lld: --notglob \"%s\" matches prior output\n",
+            p->zInFile, p->lineno, zCheck);
+        p->nTestErr++;       
+      }
+      sqlite3_free(zGlob);
+    }
+  }
+  if( !bKeep ) output_reset(p);
+#ifndef SQLITE_NOHAVE_SYSTEM
+  if( eMode=='e' || eMode=='x' || eMode=='w' ){
+    p->doXdgOpen = 1;
+    outputModePush(p);
+    if( eMode=='x' ){
+      /* spreadsheet mode.  Output as CSV. */
+      newTempFile(p, "csv");
+      p->mode.bEcho = 0;
+      p->mode.eMode = MODE_Csv;
+      modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma);
+      modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf);
+#ifdef _WIN32
+      zBom = zBomUtf8;  /* Always include the BOM on Windows, as Excel does
+                        ** not work without it. */
+#endif
+    }else if( eMode=='w' ){
+      /* web-browser mode. */
+      newTempFile(p, "html");
+      if( !bPlain ) p->mode.eMode = MODE_Www;
+    }else{
+      /* text editor mode */
+      newTempFile(p, "txt");
+    }
+    sqlite3_free(zFile);
+    zFile = sqlite3_mprintf("%s", p->zTempFile);
+  }
+#endif /* SQLITE_NOHAVE_SYSTEM */
+  if( !bKeep ) shell_check_oom(zFile);
+  if( bKeep ){
+    /* no-op */
+  }else if( cli_strcmp(zFile,"memory")==0 ){
+    if( cli_output_capture ){
+      sqlite3_free(sqlite3_str_finish(cli_output_capture));
+    }
+    cli_output_capture = sqlite3_str_new(0);
+  }else if( zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+    eputz("Error: pipes are not supported in this OS\n");
+    output_redir(p, stdout);
+    goto dotCmdOutput_error;
+#else
+    FILE *pfPipe = sqlite3_popen(zFile + 1, "w");
+    if( pfPipe==0 ){
+      assert( stderr!=NULL );
+      cli_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
+      goto dotCmdOutput_error;
+    }else{
+      output_redir(p, pfPipe);
+      if( zBom ) cli_puts(zBom, pfPipe);
+      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+    }
+#endif
+  }else{
+    FILE *pfFile = output_file_open(zFile);
+    if( pfFile==0 ){
+      if( cli_strcmp(zFile,"off")!=0 ){
+       assert( stderr!=NULL );
+       cli_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
+      }
+      goto dotCmdOutput_error;
+    } else {
+      output_redir(p, pfFile);
+      if( zBom ) cli_puts(zBom, pfFile);
+      if( bPlain && eMode=='w' ){
+        cli_puts(
+          "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n",
+          pfFile
+        );
+      }
+      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+    }
+  }
+  sqlite3_free(zFile);
+  return 0;
+
+dotCmdOutput_error:
+  sqlite3_free(zFile);
+  return 1;
+}
 
 /*
 ** If an input line begins with "." then invoke this routine to
@@ -7244,7 +7621,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
 #ifndef SQLITE_OMIT_AUTHORIZATION
   if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
     if( nArg!=2 ){
-      sqlite3_fprintf(stderr, "Usage: .auth ON|OFF\n");
+      cli_printf(stderr, "Usage: .auth ON|OFF\n");
       rc = 1;
       goto meta_command_exit;
     }
@@ -7300,19 +7677,19 @@ static int do_meta_command(const char *zLine, ShellState *p){
         zDb = zDestFile;
         zDestFile = azArg[j];
       }else{
-        sqlite3_fprintf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
+        cli_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
         return 1;
       }
     }
     if( zDestFile==0 ){
-      sqlite3_fprintf(stderr, "missing FILENAME argument on .backup\n");
+      cli_printf(stderr, "missing FILENAME argument on .backup\n");
       return 1;
     }
     if( zDb==0 ) zDb = "main";
     rc = sqlite3_open_v2(zDestFile, &pDest,
                   SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
     if( rc!=SQLITE_OK ){
-      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zDestFile);
+      cli_printf(stderr,"Error: cannot open \"%s\"\n", zDestFile);
       close_db(pDest);
       return 1;
     }
@@ -7373,7 +7750,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       rc = chdir(azArg[1]);
 #endif
       if( rc ){
-        sqlite3_fprintf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]);
+        cli_printf(stderr,"Cannot change to directory \"%s\"\n", azArg[1]);
         rc = 1;
       }
     }else{
@@ -7406,12 +7783,12 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
       rc = 2;
     }else if( testcase_glob(azArg[1],zRes)==0 ){
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
             "testcase-%s FAILED\n Expected: [%s]\n      Got: [%s]\n",
             p->zTestcase, azArg[1], zRes);
       rc = 1;
     }else{
-      sqlite3_fprintf(p->out, "testcase-%s ok\n", p->zTestcase);
+      cli_printf(p->out, "testcase-%s ok\n", p->zTestcase);
       p->nCheck++;
     }
     sqlite3_free(zRes);
@@ -7444,9 +7821,9 @@ static int do_meta_command(const char *zLine, ShellState *p){
           zFile = "(temporary-file)";
         }
         if( p->pAuxDb == &p->aAuxDb[i] ){
-          sqlite3_fprintf(stdout, "ACTIVE %d: %s\n", i, zFile);
+          cli_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
         }else if( p->aAuxDb[i].db!=0 ){
-          sqlite3_fprintf(stdout, "       %d: %s\n", i, zFile);
+          cli_printf(stdout, "       %d: %s\n", i, zFile);
         }
       }
     }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
@@ -7487,7 +7864,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       p->mode.crlfMode = 0;
 #endif
     }
-    sqlite3_fprintf(stderr, "crlf is %s\n", p->mode.crlfMode ? "ON" : "OFF");
+    cli_printf(stderr, "crlf is %s\n", p->mode.crlfMode ? "ON" : "OFF");
   }else
 
   if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
@@ -7517,7 +7894,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
       int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
       const char *z = azName[i*2+1];
-      sqlite3_fprintf(p->out, "%s: %s %s%s\n",
+      cli_printf(p->out, "%s: %s %s%s\n",
             azName[i*2], z && z[0] ? z : "\"\"", bRdonly ? "r/o" : "r/w",
             eTxn==SQLITE_TXN_NONE ? "" :
             eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
@@ -7562,7 +7939,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
       }
       sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
-      sqlite3_fprintf(p->out, "%19s %s\n",
+      cli_printf(p->out, "%19s %s\n",
                       aDbConfig[ii].zName, v ? "on" : "off");
       if( nArg>1 ) break;
     }
@@ -7653,8 +8030,8 @@ static int do_meta_command(const char *zLine, ShellState *p){
       /* When playing back a "dump", the content might appear in an order
       ** which causes immediate foreign key constraints to be violated.
       ** So disable foreign-key constraint enforcement to prevent problems. */
-      sqlite3_fputs("PRAGMA foreign_keys=OFF;\n", p->out);
-      sqlite3_fputs("BEGIN TRANSACTION;\n", p->out);
+      cli_puts("PRAGMA foreign_keys=OFF;\n", p->out);
+      cli_puts("BEGIN TRANSACTION;\n", p->out);
     }
     p->writableSchema = 0;
     p->mode.spec.bColumnNames = QRF_No;
@@ -7686,13 +8063,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }
     sqlite3_free(zLike);
     if( p->writableSchema ){
-      sqlite3_fputs("PRAGMA writable_schema=OFF;\n", p->out);
+      cli_puts("PRAGMA writable_schema=OFF;\n", p->out);
       p->writableSchema = 0;
     }
     sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
     sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
     if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
-      sqlite3_fputs(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out);
+      cli_puts(p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n", p->out);
     }
     p->shellFlgs = savedShellFlags;
     modeFree(&p->mode);
@@ -7743,7 +8120,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
 #ifndef SQLITE_SHELL_FIDDLE
   if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
-    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
+    if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) cli_exit(rc);
     rc = 2;
   }else
 #endif
@@ -7763,7 +8140,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
 #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
   if( c=='e' && cli_strncmp(azArg[0], "expert", n)==0 ){
     if( p->bSafeMode ){
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
             "Cannot run experimental commands such as \"%s\" in safe mode\n",
             azArg[0]);
       rc = 1;
@@ -7821,9 +8198,9 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
     /* --help lists all file-controls */
     if( cli_strcmp(zCmd,"help")==0 ){
-      sqlite3_fputs("Available file-controls:\n", p->out);
+      cli_puts("Available file-controls:\n", p->out);
       for(i=0; i<ArraySize(aCtrl); i++){
-        sqlite3_fprintf(p->out,
+        cli_printf(p->out,
                "  .filectrl %s %s\n", aCtrl[i].zCtrlName, aCtrl[i].zUsage);
       }
       rc = 1;
@@ -7839,7 +8216,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           filectrl = aCtrl[i].ctrlCode;
           iCtrl = i;
         }else{
-          sqlite3_fprintf(stderr,"Error: ambiguous file-control: \"%s\"\n"
+          cli_printf(stderr,"Error: ambiguous file-control: \"%s\"\n"
                 "Use \".filectrl --help\" for help\n", zCmd);
           rc = 1;
           goto meta_command_exit;
@@ -7847,7 +8224,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }
     }
     if( filectrl<0 ){
-      sqlite3_fprintf(stderr,"Error: unknown file-control: %s\n"
+      cli_printf(stderr,"Error: unknown file-control: %s\n"
             "Use \".filectrl --help\" for help\n", zCmd);
     }else{
       switch(filectrl){
@@ -7891,7 +8268,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           if( nArg!=2 ) break;
           sqlite3_file_control(p->db, zSchema, filectrl, &z);
           if( z ){
-            sqlite3_fprintf(p->out, "%s\n", z);
+            cli_printf(p->out, "%s\n", z);
             sqlite3_free(z);
           }
           isOk = 2;
@@ -7905,20 +8282,20 @@ static int do_meta_command(const char *zLine, ShellState *p){
           }
           x = -1;
           sqlite3_file_control(p->db, zSchema, filectrl, &x);
-          sqlite3_fprintf(p->out, "%d\n", x);
+          cli_printf(p->out, "%d\n", x);
           isOk = 2;
           break;
         }
       }
     }
     if( isOk==0 && iCtrl>=0 ){
-      sqlite3_fprintf(p->out, "Usage: .filectrl %s %s\n",
+      cli_printf(p->out, "Usage: .filectrl %s %s\n",
                       zCmd, aCtrl[iCtrl].zUsage);
       rc = 1;
     }else if( isOk==1 ){
       char zBuf[100];
       sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
-      sqlite3_fprintf(p->out, "%s\n", zBuf);
+      cli_printf(p->out, "%s\n", zBuf);
     }
   }else
 
@@ -7969,9 +8346,9 @@ static int do_meta_command(const char *zLine, ShellState *p){
       sqlite3_finalize(pStmt);
     }
     if( doStats==0 ){
-      sqlite3_fputs("/* No STAT tables available */\n", p->out);
+      cli_puts("/* No STAT tables available */\n", p->out);
     }else{
-      sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out);
+      cli_puts("ANALYZE sqlite_schema;\n", p->out);
       data.mode.eMode = MODE_Insert;
       if( hasStat[1] ){
         data.mode.spec.zTableName = "sqlite_stat1";
@@ -7981,7 +8358,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         data.mode.spec.zTableName = "sqlite_stat4";
         shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
       }
-      sqlite3_fputs("ANALYZE sqlite_schema;\n", p->out);
+      cli_puts("ANALYZE sqlite_schema;\n", p->out);
     }
   }else
 
@@ -7998,7 +8375,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     if( nArg>=2 ){
       n = showHelp(p->out, azArg[1]);
       if( n==0 ){
-        sqlite3_fprintf(p->out, "Nothing matches '%s'\n", azArg[1]);
+        cli_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
       }
     }else{
       showHelp(p->out, 0);
@@ -8041,7 +8418,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         }else if( zTable==0 ){
           zTable = z;
         }else{
-          sqlite3_fprintf(p->out, "ERROR: extra argument: \"%s\". Usage:\n",z);
+          cli_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n",z);
           showHelp(p->out, "import");
           goto meta_command_exit;
         }
@@ -8062,13 +8439,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
         xRead = csv_read_one_field;
         useOutputMode = 0;
       }else{
-        sqlite3_fprintf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
+        cli_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
         showHelp(p->out, "import");
         goto meta_command_exit;
       }
     }
     if( zTable==0 ){
-      sqlite3_fprintf(p->out, "ERROR: missing %s argument. Usage:\n",
+      cli_printf(p->out, "ERROR: missing %s argument. Usage:\n",
             zFile==0 ? "FILE" : "TABLE");
       showHelp(p->out, "import");
       goto meta_command_exit;
@@ -8131,19 +8508,19 @@ static int do_meta_command(const char *zLine, ShellState *p){
       sCtx.xCloser = fclose;
     }
     if( sCtx.in==0 ){
-      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zFile);
+      cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
       goto meta_command_exit;
     }
     if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
       char zSep[2];
       zSep[1] = 0;
       zSep[0] = sCtx.cColSep;
-      sqlite3_fputs("Column separator ", p->out);
+      cli_puts("Column separator ", p->out);
       output_c_string(p->out, zSep);
-      sqlite3_fputs(", row separator ", p->out);
+      cli_puts(", row separator ", p->out);
       zSep[0] = sCtx.cRowSep;
       output_c_string(p->out, zSep);
-      sqlite3_fputs("\n", p->out);
+      cli_puts("\n", p->out);
     }
     sCtx.z = sqlite3_malloc64(120);
     if( sCtx.z==0 ){
@@ -8173,14 +8550,14 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }
       zColDefs = zAutoColumn(0, &dbCols, &zRenames);
       if( zRenames!=0 ){
-        sqlite3_fprintf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
+        cli_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
               "Columns renamed during .import %s due to duplicates:\n"
               "%s\n", sCtx.zFile, zRenames);
         sqlite3_free(zRenames);
       }
       assert(dbCols==0);
       if( zColDefs==0 ){
-        sqlite3_fprintf(stderr,"%s: empty file\n", sCtx.zFile);
+        cli_printf(stderr,"%s: empty file\n", sCtx.zFile);
         import_cleanup(&sCtx);
         rc = 1;
         sqlite3_free(zCreate);
@@ -8192,11 +8569,11 @@ static int do_meta_command(const char *zLine, ShellState *p){
         shell_out_of_memory();
       }
       if( eVerbose>=1 ){
-        sqlite3_fprintf(p->out, "%s\n", zCreate);
+        cli_printf(p->out, "%s\n", zCreate);
       }
       rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
       if( rc ){
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
              "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
       }
       sqlite3_free(zCreate);
@@ -8256,7 +8633,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     zSql[j] = 0;
     assert( j<nByte );
     if( eVerbose>=2 ){
-      sqlite3_fprintf(p->out, "Insert using: %s\n", zSql);
+      cli_printf(p->out, "Insert using: %s\n", zSql);
     }
     rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
     sqlite3_free(zSql);
@@ -8295,7 +8672,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         }
         sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
         if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
-          sqlite3_fprintf(stderr,"%s:%d: expected %d columns but found %d"
+          cli_printf(stderr,"%s:%d: expected %d columns but found %d"
                 " - filling the rest with NULL\n",
                 sCtx.zFile, startLine, nCol, i+1);
           i += 2;
@@ -8307,7 +8684,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           xRead(&sCtx);
           i++;
         }while( sCtx.cTerm==sCtx.cColSep );
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
               "%s:%d: expected %d columns but found %d - extras ignored\n",
               sCtx.zFile, startLine, nCol, i);
       }
@@ -8315,7 +8692,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         sqlite3_step(pStmt);
         rc = sqlite3_reset(pStmt);
         if( rc!=SQLITE_OK ){
-          sqlite3_fprintf(stderr,"%s:%d: INSERT failed: %s\n",
+          cli_printf(stderr,"%s:%d: INSERT failed: %s\n",
                 sCtx.zFile, startLine, sqlite3_errmsg(p->db));
           sCtx.nErr++;
         }else{
@@ -8328,7 +8705,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     sqlite3_finalize(pStmt);
     if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
     if( eVerbose>0 ){
-      sqlite3_fprintf(p->out,
+      cli_printf(p->out,
             "Added %d rows with %d errors using %d lines of input\n",
             sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
     }
@@ -8405,7 +8782,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }
     sqlite3_finalize(pStmt);
     if( i==0 || tnum==0 ){
-      sqlite3_fprintf(stderr,"no such index: \"%s\"\n", azArg[1]);
+      cli_printf(stderr,"no such index: \"%s\"\n", azArg[1]);
       rc = 1;
       sqlite3_free(zCollist);
       goto meta_command_exit;
@@ -8420,13 +8797,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
       rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
       sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
       if( rc ){
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
               "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
       }else{
-        sqlite3_fprintf(stdout, "%s;\n", zSql);
+        cli_printf(stdout, "%s;\n", zSql);
       }
     }else{
-      sqlite3_fprintf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
+      cli_printf(stderr,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
       rc = 1;
     }
     sqlite3_free(zSql);
@@ -8440,7 +8817,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       if( iArg==0 ) iArg = -1;
     }
     if( (nArg!=1 && nArg!=2) || iArg<0 ){
-      sqlite3_fprintf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n");
+      cli_printf(stderr,"%s","Usage: .intck STEPS_PER_UNLOCK\n");
       rc = 1;
       goto meta_command_exit;
     }
@@ -8461,7 +8838,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }else{
       iotrace = sqlite3_fopen(azArg[1], "w");
       if( iotrace==0 ){
-        sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
+        cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
         sqlite3IoTrace = 0;
         rc = 1;
       }else{
@@ -8493,7 +8870,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     open_db(p, 0);
     if( nArg==1 ){
       for(i=0; i<ArraySize(aLimit); i++){
-        sqlite3_fprintf(stdout, "%20s %d\n", aLimit[i].zLimitName,
+        cli_printf(stdout, "%20s %d\n", aLimit[i].zLimitName,
               sqlite3_limit(p->db, aLimit[i].limitCode, -1));
       }
     }else if( nArg>3 ){
@@ -8508,14 +8885,14 @@ static int do_meta_command(const char *zLine, ShellState *p){
           if( iLimit<0 ){
             iLimit = i;
           }else{
-            sqlite3_fprintf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]);
+            cli_printf(stderr,"ambiguous limit: \"%s\"\n", azArg[1]);
             rc = 1;
             goto meta_command_exit;
           }
         }
       }
       if( iLimit<0 ){
-        sqlite3_fprintf(stderr,"unknown limit: \"%s\"\n"
+        cli_printf(stderr,"unknown limit: \"%s\"\n"
               "enter \".limits\" with no arguments for a list.\n",
               azArg[1]);
         rc = 1;
@@ -8525,7 +8902,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         sqlite3_limit(p->db, aLimit[iLimit].limitCode,
                       (int)integerValue(azArg[2]));
       }
-      sqlite3_fprintf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName,
+      cli_printf(stdout, "%20s %d\n", aLimit[iLimit].zLimitName,
             sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
     }
   }else
@@ -8588,9 +8965,9 @@ static int do_meta_command(const char *zLine, ShellState *p){
       eputz("Usage: .nonce NONCE\n");
       rc = 1;
     }else if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){
-      sqlite3_fprintf(stderr,"line %lld: incorrect nonce: \"%s\"\n",
+      cli_printf(stderr,"line %lld: incorrect nonce: \"%s\"\n",
             p->lineno, azArg[1]);
-      exit(1);
+      cli_exit(1);
     }else{
       p->bSafeMode = 0;
       return 0;  /* Return immediately to bypass the safe mode reset
@@ -8650,11 +9027,11 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }else
 #endif /* !SQLITE_SHELL_FIDDLE */
       if( z[0]=='-' ){
-        sqlite3_fprintf(stderr,"unknown option: %s\n", z);
+        cli_printf(stderr,"unknown option: %s\n", z);
         rc = 1;
         goto meta_command_exit;
       }else if( zFN ){
-        sqlite3_fprintf(stderr,"extra argument: \"%s\"\n", z);
+        cli_printf(stderr,"extra argument: \"%s\"\n", z);
         rc = 1;
         goto meta_command_exit;
       }else{
@@ -8705,7 +9082,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       p->pAuxDb->zDbFilename = zNewFilename;
       open_db(p, OPEN_DB_KEEPALIVE);
       if( p->db==0 ){
-        sqlite3_fprintf(stderr,"Error: cannot open '%s'\n", zNewFilename);
+        cli_printf(stderr,"Error: cannot open '%s'\n", zNewFilename);
         sqlite3_free(zNewFilename);
       }else{
         p->pAuxDb->zFreeOnClose = zNewFilename;
@@ -8725,145 +9102,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
    || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0)
    || (c=='w' && n==3 && cli_strcmp(azArg[0],"www")==0)
   ){
-    char *zFile = 0;
-    int i;
-    int eMode = 0;          /* 0: .outout/.once, 'x'=.excel, 'w'=.www */
-    int bOnce = 0;          /* 0: .output, 1: .once, 2: .excel/.www */
-    int bPlain = 0;         /* --plain option */
-    static const char *zBomUtf8 = "\357\273\277";
-    const char *zBom = 0;
-
-    failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
-    if( c=='e' ){
-      eMode = 'x';
-      bOnce = 2;
-    }else if( c=='w' ){
-      eMode = 'w';
-      bOnce = 2;
-    }else if( cli_strncmp(azArg[0],"once",n)==0 ){
-      bOnce = 1;
-    }
-    for(i=1; i<nArg; i++){
-      char *z = azArg[i];
-      if( z[0]=='-' ){
-        if( z[1]=='-' ) z++;
-        if( cli_strcmp(z,"-bom")==0 ){
-          zBom = zBomUtf8;
-        }else if( cli_strcmp(z,"-plain")==0 ){
-          bPlain = 1;
-        }else if( c=='o' && cli_strcmp(z,"-x")==0 ){
-          eMode = 'x';  /* spreadsheet */
-        }else if( c=='o' && cli_strcmp(z,"-e")==0 ){
-          eMode = 'e';  /* text editor */
-        }else if( c=='o' && cli_strcmp(z,"-w")==0 ){
-          eMode = 'w';  /* Web browser */
-        }else{
-          sqlite3_fprintf(p->out,
-                          "ERROR: unknown option: \"%s\". Usage:\n", azArg[i]);
-          showHelp(p->out, azArg[0]);
-          rc = 1;
-          sqlite3_free(zFile);
-          goto meta_command_exit;
-        }
-      }else if( zFile==0 && eMode==0 ){
-        if( cli_strcmp(z, "off")==0 ){
-#ifdef _WIN32
-          zFile = sqlite3_mprintf("nul");
-#else
-          zFile = sqlite3_mprintf("/dev/null");
-#endif
-        }else{
-          zFile = sqlite3_mprintf("%s", z);
-        }
-        if( zFile && zFile[0]=='|' ){
-          while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
-          break;
-        }
-      }else{
-        sqlite3_fprintf(p->out,
-            "ERROR: extra parameter: \"%s\".  Usage:\n", azArg[i]);
-        showHelp(p->out, azArg[0]);
-        rc = 1;
-        sqlite3_free(zFile);
-        goto meta_command_exit;
-      }
-    }
-    if( zFile==0 ){
-      zFile = sqlite3_mprintf("stdout");
-    }
-    shell_check_oom(zFile);
-    if( bOnce ){
-      p->outCount = 2;
-    }else{
-      p->outCount = 0;
-    }
-    output_reset(p);
-#ifndef SQLITE_NOHAVE_SYSTEM
-    if( eMode=='e' || eMode=='x' || eMode=='w' ){
-      p->doXdgOpen = 1;
-      outputModePush(p);
-      if( eMode=='x' ){
-        /* spreadsheet mode.  Output as CSV. */
-        newTempFile(p, "csv");
-        p->mode.bEcho = 0;
-        p->mode.eMode = MODE_Csv;
-        modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma);
-        modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf);
-#ifdef _WIN32
-        zBom = zBomUtf8;  /* Always include the BOM on Windows, as Excel does
-                          ** not work without it. */
-#endif
-      }else if( eMode=='w' ){
-        /* web-browser mode. */
-        newTempFile(p, "html");
-        if( !bPlain ) p->mode.eMode = MODE_Www;
-      }else{
-        /* text editor mode */
-        newTempFile(p, "txt");
-      }
-      sqlite3_free(zFile);
-      zFile = sqlite3_mprintf("%s", p->zTempFile);
-    }
-#endif /* SQLITE_NOHAVE_SYSTEM */
-    shell_check_oom(zFile);
-    if( zFile[0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
-      eputz("Error: pipes are not supported in this OS\n");
-      rc = 1;
-      output_redir(p, stdout);
-#else
-      FILE *pfPipe = sqlite3_popen(zFile + 1, "w");
-      if( pfPipe==0 ){
-        assert( stderr!=NULL );
-        sqlite3_fprintf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
-        rc = 1;
-      }else{
-        output_redir(p, pfPipe);
-        if( zBom ) sqlite3_fputs(zBom, pfPipe);
-        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
-      }
-#endif
-    }else{
-      FILE *pfFile = output_file_open(zFile);
-      if( pfFile==0 ){
-        if( cli_strcmp(zFile,"off")!=0 ){
-         assert( stderr!=NULL );
-         sqlite3_fprintf(stderr,"Error: cannot write to \"%s\"\n", zFile);
-        }
-        rc = 1;
-      } else {
-        output_redir(p, pfFile);
-        if( zBom ) sqlite3_fputs(zBom, pfFile);
-        if( bPlain && eMode=='w' ){
-          sqlite3_fputs(
-            "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n",
-            pfFile
-          );
-        }
-        sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
-      }
-    }
-    sqlite3_free(zFile);
+    rc = dotCmdOutput(p);
   }else
 #endif /* !defined(SQLITE_SHELL_FIDDLE) */
 
@@ -8900,7 +9139,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
              "SELECT key, quote(value) "
              "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
         while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-          sqlite3_fprintf(p->out,
+          cli_printf(p->out,
                 "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
                 sqlite3_column_text(pStmt,1));
         }
@@ -8946,7 +9185,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
         sqlite3_free(zSql);
         if( rx!=SQLITE_OK ){
-          sqlite3_fprintf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
+          cli_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
           sqlite3_finalize(pStmt);
           pStmt = 0;
           rc = 1;
@@ -8976,10 +9215,10 @@ static int do_meta_command(const char *zLine, ShellState *p){
   if( c=='p' && n>=3 && cli_strncmp(azArg[0], "print", n)==0 ){
     int i;
     for(i=1; i<nArg; i++){
-      if( i>1 ) sqlite3_fputs(" ", p->out);
-      sqlite3_fputs(azArg[i], p->out);
+      if( i>1 ) cli_puts(" ", p->out);
+      cli_puts(azArg[i], p->out);
     }
-    sqlite3_fputs("\n", p->out);
+    cli_puts("\n", p->out);
   }else
 
 #ifndef SQLITE_OMIT_PROGRESS_CALLBACK
@@ -9016,7 +9255,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           }
           continue;
         }
-        sqlite3_fprintf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]);
+        cli_printf(stderr,"Error: unknown option: \"%s\"\n", azArg[i]);
         rc = 1;
         goto meta_command_exit;
       }else{
@@ -9060,7 +9299,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
 #else
       p->in = sqlite3_popen(azArg[1]+1, "r");
       if( p->in==0 ){
-        sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
+        cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
         rc = 1;
       }else{
         rc = process_input(p, "<pipe>");
@@ -9068,7 +9307,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }
 #endif
     }else if( (p->in = openChrSource(azArg[1]))==0 ){
-      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
+      cli_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
       rc = 1;
     }else{
       char *zFilename = strdup(azArg[1]);
@@ -9103,7 +9342,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }
     rc = sqlite3_open(zSrcFile, &pSrc);
     if( rc!=SQLITE_OK ){
-      sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", zSrcFile);
+      cli_printf(stderr,"Error: cannot open \"%s\"\n", zSrcFile);
       close_db(pSrc);
       return 1;
     }
@@ -9191,7 +9430,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }else if( optionMatch(azArg[ii],"nosys") ){
         bNoSystemTabs = 1;
       }else if( azArg[ii][0]=='-' ){
-        sqlite3_fprintf(stderr,"Unknown option: \"%s\"\n", azArg[ii]);
+        cli_printf(stderr,"Unknown option: \"%s\"\n", azArg[ii]);
         rc = 1;
         goto meta_command_exit;
       }else if( zName==0 ){
@@ -9208,7 +9447,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
                   || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
                   || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
       if( isSchema ){
-        sqlite3_fprintf(p->out,
+        cli_printf(p->out,
                       "CREATE TABLE %s (\n"
                       "  type text,\n"
                       "  name text,\n"
@@ -9280,7 +9519,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     }
     sqlite3_str_appendf(pSql, "sql IS NOT NULL ORDER BY snum, rowid");
     if( bDebug ){
-      sqlite3_fprintf(p->out, "SQL: %s;\n", sqlite3_str_value(pSql));
+      cli_printf(p->out, "SQL: %s;\n", sqlite3_str_value(pSql));
     }else{
       rc = shell_exec(&data, sqlite3_str_value(pSql), &zErrMsg);
     }
@@ -9341,7 +9580,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }else{
         rc = sqlite3session_attach(pSession->p, azCmd[1]);
         if( rc ){
-          sqlite3_fprintf(stderr,
+          cli_printf(stderr,
                "ERROR: sqlite3session_attach() returns %d\n",rc);
           rc = 0;
         }
@@ -9361,7 +9600,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       if( pSession->p==0 ) goto session_not_open;
       out = sqlite3_fopen(azCmd[1], "wb");
       if( out==0 ){
-        sqlite3_fprintf(stderr,"ERROR: cannot open \"%s\" for writing\n",
+        cli_printf(stderr,"ERROR: cannot open \"%s\" for writing\n",
               azCmd[1]);
       }else{
         int szChng;
@@ -9372,12 +9611,12 @@ static int do_meta_command(const char *zLine, ShellState *p){
           rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
         }
         if( rc ){
-          sqlite3_fprintf(stdout, "Error: error code %d\n", rc);
+          cli_printf(stdout, "Error: error code %d\n", rc);
           rc = 0;
         }
         if( pChng
           && fwrite(pChng, szChng, 1, out)!=1 ){
-          sqlite3_fprintf(stderr,
+          cli_printf(stderr,
               "ERROR: Failed to write entire %d-byte output\n", szChng);
         }
         sqlite3_free(pChng);
@@ -9405,7 +9644,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
       if( pAuxDb->nSession ){
         ii = sqlite3session_enable(pSession->p, ii);
-        sqlite3_fprintf(p->out,
+        cli_printf(p->out,
             "session %s enable flag = %d\n", pSession->zName, ii);
       }
     }else
@@ -9442,7 +9681,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
       if( pAuxDb->nSession ){
         ii = sqlite3session_indirect(pSession->p, ii);
-        sqlite3_fprintf(p->out,
+        cli_printf(p->out,
             "session %s indirect flag = %d\n", pSession->zName, ii);
       }
     }else
@@ -9455,7 +9694,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       if( nCmd!=1 ) goto session_syntax_error;
       if( pAuxDb->nSession ){
         ii = sqlite3session_isempty(pSession->p);
-        sqlite3_fprintf(p->out,
+        cli_printf(p->out,
              "session %s isempty flag = %d\n", pSession->zName, ii);
       }
     }else
@@ -9465,7 +9704,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     */
     if( cli_strcmp(azCmd[0],"list")==0 ){
       for(i=0; i<pAuxDb->nSession; i++){
-        sqlite3_fprintf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
+        cli_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
       }
     }else
 
@@ -9480,19 +9719,19 @@ static int do_meta_command(const char *zLine, ShellState *p){
       if( zName[0]==0 ) goto session_syntax_error;
       for(i=0; i<pAuxDb->nSession; i++){
         if( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
-          sqlite3_fprintf(stderr,"Session \"%s\" already exists\n", zName);
+          cli_printf(stderr,"Session \"%s\" already exists\n", zName);
           goto meta_command_exit;
         }
       }
       if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
            "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
         goto meta_command_exit;
       }
       pSession = &pAuxDb->aSession[pAuxDb->nSession];
       rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
       if( rc ){
-        sqlite3_fprintf(stderr,"Cannot open session: error code=%d\n", rc);
+        cli_printf(stderr,"Cannot open session: error code=%d\n", rc);
         rc = 0;
         goto meta_command_exit;
       }
@@ -9516,7 +9755,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       int i, v;
       for(i=1; i<nArg; i++){
         v = booleanValue(azArg[i]);
-        sqlite3_fprintf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
+        cli_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
       }
     }
     if( cli_strncmp(azArg[0]+9, "integer", n-9)==0 ){
@@ -9525,7 +9764,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         char zBuf[200];
         v = integerValue(azArg[i]);
         sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
-        sqlite3_fputs(zBuf, p->out);
+        cli_puts(zBuf, p->out);
       }
     }
   }else
@@ -9552,9 +9791,9 @@ static int do_meta_command(const char *zLine, ShellState *p){
         bVerbose++;
       }else
       {
-        sqlite3_fprintf(stderr,
+        cli_printf(stderr,
               "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]);
-        sqlite3_fputs("Should be one of: --init -v\n", stderr);
+        cli_puts("Should be one of: --init -v\n", stderr);
         rc = 1;
         goto meta_command_exit;
       }
@@ -9599,10 +9838,10 @@ static int do_meta_command(const char *zLine, ShellState *p){
         if( zAns==0 ) continue;
         k = 0;
         if( bVerbose>0 ){
-          sqlite3_fprintf(stdout, "%d: %s %s\n", tno, zOp, zSql);
+          cli_printf(stdout, "%d: %s %s\n", tno, zOp, zSql);
         }
         if( cli_strcmp(zOp,"memo")==0 ){
-          sqlite3_fprintf(p->out, "%s\n", zSql);
+          cli_printf(p->out, "%s\n", zSql);
         }else
         if( cli_strcmp(zOp,"run")==0 ){
           char *zErrMsg = 0;
@@ -9611,22 +9850,22 @@ static int do_meta_command(const char *zLine, ShellState *p){
           rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
           nTest++;
           if( bVerbose ){
-            sqlite3_fprintf(p->out, "Result: %s\n", str.zTxt);
+            cli_printf(p->out, "Result: %s\n", str.zTxt);
           }
           if( rc || zErrMsg ){
             nErr++;
             rc = 1;
-            sqlite3_fprintf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg);
+            cli_printf(p->out, "%d: error-code-%d: %s\n", tno, rc,zErrMsg);
             sqlite3_free(zErrMsg);
           }else if( cli_strcmp(zAns,str.zTxt)!=0 ){
             nErr++;
             rc = 1;
-            sqlite3_fprintf(p->out, "%d: Expected: [%s]\n", tno, zAns);
-            sqlite3_fprintf(p->out, "%d:      Got: [%s]\n", tno, str.zTxt);
+            cli_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
+            cli_printf(p->out, "%d:      Got: [%s]\n", tno, str.zTxt);
           }
         }
         else{
-          sqlite3_fprintf(stderr,
+          cli_printf(stderr,
                 "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
           rc = 1;
           break;
@@ -9635,7 +9874,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       sqlite3_finalize(pStmt);
     } /* End loop over k */
     freeText(&str);
-    sqlite3_fprintf(p->out, "%d errors out of %d tests\n", nErr, nTest);
+    cli_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
   }else
 
   if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){
@@ -9681,7 +9920,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           bDebug = 1;
         }else
         {
-          sqlite3_fprintf(stderr,
+          cli_printf(stderr,
                   "Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]);
           showHelp(p->out, azArg[0]);
           rc = 1;
@@ -9760,7 +9999,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     freeText(&sQuery);
     freeText(&sSql);
     if( bDebug ){
-      sqlite3_fprintf(p->out, "%s\n", zSql);
+      cli_printf(p->out, "%s\n", zSql);
     }else{
       shell_exec(p, zSql, 0);
     }
@@ -9790,7 +10029,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           "' OR ') as query, tname from tabcols group by tname)"
           , zRevText);
       shell_check_oom(zRevText);
-      if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zRevText);
+      if( bDebug ) cli_printf(p->out, "%s\n", zRevText);
       lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0);
       if( lrc!=SQLITE_OK ){
         /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the
@@ -9803,7 +10042,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
           sqlite3_stmt *pCheckStmt;
           lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0);
-          if( bDebug ) sqlite3_fprintf(p->out, "%s\n", zGenQuery);
+          if( bDebug ) cli_printf(p->out, "%s\n", zGenQuery);
           if( lrc!=SQLITE_OK ){
             rc = 1;
           }else{
@@ -9811,7 +10050,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
               double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
               if( countIrreversible>0 ){
                 int sz = (int)(countIrreversible + 0.5);
-                sqlite3_fprintf(stderr,
+                cli_printf(stderr,
                       "Digest includes %d invalidly encoded text field%s.\n",
                       sz, (sz>1)? "s": "");
               }
@@ -9850,7 +10089,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     x = zCmd!=0 ? system(zCmd) : 1;
     /*consoleRenewSetup();*/
     sqlite3_free(zCmd);
-    if( x ) sqlite3_fprintf(stderr,"System command returns %d\n", x);
+    if( x ) cli_printf(stderr,"System command returns %d\n", x);
   }else
 #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
 
@@ -9863,49 +10102,49 @@ static int do_meta_command(const char *zLine, ShellState *p){
       rc = 1;
       goto meta_command_exit;
     }
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","echo", azBool[p->mode.bEcho!=0]);
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]);
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","explain",
+    cli_printf(p->out, "%12.12s: %s\n","echo", azBool[p->mode.bEcho!=0]);
+    cli_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]);
+    cli_printf(p->out, "%12.12s: %s\n","explain",
                              p->mode.autoExplain ? "auto" : "off");
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","headers",
+    cli_printf(p->out, "%12.12s: %s\n","headers",
           azBool[p->mode.spec.bColumnNames==QRF_Yes]);
     if( p->mode.spec.eStyle==QRF_STYLE_Column
      || p->mode.spec.eStyle==QRF_STYLE_Box
      || p->mode.spec.eStyle==QRF_STYLE_Table
      || p->mode.spec.eStyle==QRF_STYLE_Markdown
     ){
-      sqlite3_fprintf(p->out,
+      cli_printf(p->out,
             "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
             modeDescr[p->mode.eMode], p->mode.spec.mxColWidth,
             p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off",
             p->mode.spec.eText==QRF_TEXT_Sql ? "" : "no");
     }else{
-      sqlite3_fprintf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode.eMode]);
+      cli_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode.eMode]);
     }
-    sqlite3_fprintf(p->out, "%12.12s: ", "nullvalue");
+    cli_printf(p->out, "%12.12s: ", "nullvalue");
     output_c_string(p->out, p->mode.spec.zNull);
-    sqlite3_fputs("\n", p->out);
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","output",
+    cli_puts("\n", p->out);
+    cli_printf(p->out, "%12.12s: %s\n","output",
           strlen30(p->outfile) ? p->outfile : "stdout");
-    sqlite3_fprintf(p->out, "%12.12s: ", "colseparator");
+    cli_printf(p->out, "%12.12s: ", "colseparator");
     output_c_string(p->out, p->mode.spec.zColumnSep);
-    sqlite3_fputs("\n", p->out);
-    sqlite3_fprintf(p->out, "%12.12s: ", "rowseparator");
+    cli_puts("\n", p->out);
+    cli_printf(p->out, "%12.12s: ", "rowseparator");
     output_c_string(p->out, p->mode.spec.zRowSep);
-    sqlite3_fputs("\n", p->out);
+    cli_puts("\n", p->out);
     switch( p->statsOn ){
       case 0:  zOut = "off";     break;
       default: zOut = "on";      break;
       case 2:  zOut = "stmt";    break;
       case 3:  zOut = "vmstep";  break;
     }
-    sqlite3_fprintf(p->out, "%12.12s: %s\n","stats", zOut);
-    sqlite3_fprintf(p->out, "%12.12s: ", "width");
+    cli_printf(p->out, "%12.12s: %s\n","stats", zOut);
+    cli_printf(p->out, "%12.12s: ", "width");
     for(i=0; i<p->mode.spec.nWidth; i++){
-      sqlite3_fprintf(p->out, "%d ", (int)p->mode.spec.aWidth[i]);
+      cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]);
     }
-    sqlite3_fputs("\n", p->out);
-    sqlite3_fprintf(p->out, "%12.12s: %s\n", "filename",
+    cli_puts("\n", p->out);
+    cli_printf(p->out, "%12.12s: %s\n", "filename",
           p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
   }else
 
@@ -10023,10 +10262,10 @@ static int do_meta_command(const char *zLine, ShellState *p){
       for(i=0; i<nPrintRow; i++){
         for(j=i; j<nRow; j+=nPrintRow){
           char *zSp = j<nPrintRow ? "" : "  ";
-          sqlite3_fprintf(p->out,
+          cli_printf(p->out,
                "%s%-*s", zSp, maxlen, azResult[j] ? azResult[j]:"");
         }
-        sqlite3_fputs("\n", p->out);
+        cli_puts("\n", p->out);
       }
     }
 
@@ -10101,10 +10340,10 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
     /* --help lists all test-controls */
     if( cli_strcmp(zCmd,"help")==0 ){
-      sqlite3_fputs("Available test-controls:\n", p->out);
+      cli_puts("Available test-controls:\n", p->out);
       for(i=0; i<ArraySize(aCtrl); i++){
         if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
-        sqlite3_fprintf(p->out, "  .testctrl %s %s\n",
+        cli_printf(p->out, "  .testctrl %s %s\n",
               aCtrl[i].zCtrlName, aCtrl[i].zUsage);
       }
       rc = 1;
@@ -10121,7 +10360,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           testctrl = aCtrl[i].ctrlCode;
           iCtrl = i;
         }else{
-          sqlite3_fprintf(stderr,"Error: ambiguous test-control: \"%s\"\n"
+          cli_printf(stderr,"Error: ambiguous test-control: \"%s\"\n"
                 "Use \".testctrl --help\" for help\n", zCmd);
           rc = 1;
           goto meta_command_exit;
@@ -10129,7 +10368,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }
     }
     if( testctrl<0 ){
-      sqlite3_fprintf(stderr,"Error: unknown test-control: %s\n"
+      cli_printf(stderr,"Error: unknown test-control: %s\n"
             "Use \".testctrl --help\" for help\n", zCmd);
     }else{
       switch(testctrl){
@@ -10209,13 +10448,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
                 if( sqlite3_stricmp(zLabel, aLabel[jj].zLabel)==0 ) break;
               }
               if( jj>=ArraySize(aLabel) ){
-                sqlite3_fprintf(stderr,
+                cli_printf(stderr,
                     "Error: no such optimization: \"%s\"\n", zLabel);
-                sqlite3_fputs("Should be one of:", stderr);
+                cli_puts("Should be one of:", stderr);
                 for(jj=0; jj<ArraySize(aLabel); jj++){
-                  sqlite3_fprintf(stderr," %s", aLabel[jj].zLabel);
+                  cli_printf(stderr," %s", aLabel[jj].zLabel);
                 }
-                sqlite3_fputs("\n", stderr);
+                cli_puts("\n", stderr);
                 rc = 1;
                 goto meta_command_exit;
               }
@@ -10233,23 +10472,23 @@ static int do_meta_command(const char *zLine, ShellState *p){
             if( m & newOpt ) nOff++;
           }
           if( nOff<12 ){
-            sqlite3_fputs("+All", p->out);
+            cli_puts("+All", p->out);
             for(ii=0; ii<ArraySize(aLabel); ii++){
               if( !aLabel[ii].bDsply  ) continue;
               if( (newOpt & aLabel[ii].mask)!=0 ){
-                sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel);
+                cli_printf(p->out, " -%s", aLabel[ii].zLabel);
               }
             }
           }else{
-            sqlite3_fputs("-All", p->out);
+            cli_puts("-All", p->out);
             for(ii=0; ii<ArraySize(aLabel); ii++){
               if( !aLabel[ii].bDsply  ) continue;
               if( (newOpt & aLabel[ii].mask)==0 ){
-                sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel);
+                cli_printf(p->out, " +%s", aLabel[ii].zLabel);
               }
             }
           }
-          sqlite3_fputs("\n", p->out);
+          cli_puts("\n", p->out);
           rc2 = isOk = 3;
           break;
         }
@@ -10289,7 +10528,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
             sqlite3 *db;
             if( ii==0 && cli_strcmp(azArg[2],"random")==0 ){
               sqlite3_randomness(sizeof(ii),&ii);
-              sqlite3_fprintf(stdout, "-- random seed: %d\n", ii);
+              cli_printf(stdout, "-- random seed: %d\n", ii);
             }
             if( nArg==3 ){
               db = 0;
@@ -10342,7 +10581,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         case SQLITE_TESTCTRL_SEEK_COUNT: {
           u64 x = 0;
           rc2 = sqlite3_test_control(testctrl, p->db, &x);
-          sqlite3_fprintf(p->out, "%llu\n", x);
+          cli_printf(p->out, "%llu\n", x);
           isOk = 3;
           break;
         }
@@ -10373,11 +10612,11 @@ static int do_meta_command(const char *zLine, ShellState *p){
               int val = 0;
               rc2 = sqlite3_test_control(testctrl, -id, &val);
               if( rc2!=SQLITE_OK ) break;
-              if( id>1 ) sqlite3_fputs("  ", p->out);
-              sqlite3_fprintf(p->out, "%d: %d", id, val);
+              if( id>1 ) cli_puts("  ", p->out);
+              cli_printf(p->out, "%d: %d", id, val);
               id++;
             }
-            if( id>1 ) sqlite3_fputs("\n", p->out);
+            if( id>1 ) cli_puts("\n", p->out);
             isOk = 3;
           }
           break;
@@ -10416,7 +10655,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           int ii, jj, x;
           int *aOp;
           if( nArg!=4 ){
-            sqlite3_fprintf(stderr,
+            cli_printf(stderr,
               "ERROR - should be:  \".testctrl bitvec_test SIZE  INT-ARRAY\"\n"
             );
             rc = 1;
@@ -10439,7 +10678,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           }
           aOp[jj] = x;
           x = sqlite3_test_control(testctrl, iSize, aOp);
-          sqlite3_fprintf(p->out, "result: %d\n", x);
+          cli_printf(p->out, "result: %d\n", x);
           free(aOp);
           break;
         }
@@ -10462,21 +10701,21 @@ static int do_meta_command(const char *zLine, ShellState *p){
               faultsim_state.nHit = 0;
               sqlite3_test_control(testctrl, faultsim_callback);
             }else if( cli_strcmp(z,"status")==0 ){
-              sqlite3_fprintf(p->out, "faultsim.iId:       %d\n",
+              cli_printf(p->out, "faultsim.iId:       %d\n",
                               faultsim_state.iId);
-              sqlite3_fprintf(p->out, "faultsim.iErr:      %d\n",
+              cli_printf(p->out, "faultsim.iErr:      %d\n",
                               faultsim_state.iErr);
-              sqlite3_fprintf(p->out, "faultsim.iCnt:      %d\n",
+              cli_printf(p->out, "faultsim.iCnt:      %d\n",
                               faultsim_state.iCnt);
-              sqlite3_fprintf(p->out, "faultsim.nHit:      %d\n",
+              cli_printf(p->out, "faultsim.nHit:      %d\n",
                               faultsim_state.nHit);
-              sqlite3_fprintf(p->out, "faultsim.iInterval: %d\n",
+              cli_printf(p->out, "faultsim.iInterval: %d\n",
                               faultsim_state.iInterval);
-              sqlite3_fprintf(p->out, "faultsim.eVerbose:  %d\n",
+              cli_printf(p->out, "faultsim.eVerbose:  %d\n",
                               faultsim_state.eVerbose);
-              sqlite3_fprintf(p->out, "faultsim.nRepeat:   %d\n",
+              cli_printf(p->out, "faultsim.nRepeat:   %d\n",
                               faultsim_state.nRepeat);
-              sqlite3_fprintf(p->out, "faultsim.nSkip:     %d\n",
+              cli_printf(p->out, "faultsim.nSkip:     %d\n",
                               faultsim_state.nSkip);
             }else if( cli_strcmp(z,"-v")==0 ){
               if( faultsim_state.eVerbose<2 ) faultsim_state.eVerbose++;
@@ -10495,7 +10734,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
             }else if( cli_strcmp(z,"-?")==0 || sqlite3_strglob("*help*",z)==0){
               bShowHelp = 1;
             }else{
-              sqlite3_fprintf(stderr,
+              cli_printf(stderr,
                   "Unrecognized fault_install argument: \"%s\"\n",
                   azArg[kk]);
               rc = 1;
@@ -10504,7 +10743,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
             }
           }
           if( bShowHelp ){
-            sqlite3_fputs(
+            cli_puts(
                "Usage: .testctrl fault_install ARGS\n"
                "Possible arguments:\n"
                "   off               Disable faultsim\n"
@@ -10526,13 +10765,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
       }
     }
     if( isOk==0 && iCtrl>=0 ){
-      sqlite3_fprintf(p->out,
+      cli_printf(p->out,
           "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
       rc = 1;
     }else if( isOk==1 ){
-      sqlite3_fprintf(p->out, "%d\n", rc2);
+      cli_printf(p->out, "%d\n", rc2);
     }else if( isOk==2 ){
-      sqlite3_fprintf(p->out, "0x%08x\n", rc2);
+      cli_printf(p->out, "0x%08x\n", rc2);
     }
   }else
 #endif /* !defined(SQLITE_UNTESTABLE) */
@@ -10587,7 +10826,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
           mType |= SQLITE_TRACE_CLOSE;
         }
         else {
-          sqlite3_fprintf(stderr,"Unknown option \"%s\" on \".trace\"\n", z);
+          cli_printf(stderr,"Unknown option \"%s\" on \".trace\"\n", z);
           rc = 1;
           goto meta_command_exit;
         }
@@ -10632,21 +10871,21 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
   if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){
     char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit";
-    sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
+    cli_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
           sqlite3_libversion(), sqlite3_sourceid());
 #if SQLITE_HAVE_ZLIB
-    sqlite3_fprintf(p->out, "zlib version %s\n", zlibVersion());
+    cli_printf(p->out, "zlib version %s\n", zlibVersion());
 #endif
 #define CTIMEOPT_VAL_(opt) #opt
 #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
 #if defined(__clang__) && defined(__clang_major__)
-    sqlite3_fprintf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
+    cli_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
           CTIMEOPT_VAL(__clang_minor__) "."
           CTIMEOPT_VAL(__clang_patchlevel__) " (%s)\n", zPtrSz);
 #elif defined(_MSC_VER)
-    sqlite3_fprintf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz);
+    cli_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) " (%s)\n", zPtrSz);
 #elif defined(__GNUC__) && defined(__VERSION__)
-    sqlite3_fprintf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz);
+    cli_printf(p->out, "gcc-" __VERSION__ " (%s)\n", zPtrSz);
 #endif
   }else
 
@@ -10656,10 +10895,10 @@ static int do_meta_command(const char *zLine, ShellState *p){
     if( p->db ){
       sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
       if( pVfs ){
-        sqlite3_fprintf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
-        sqlite3_fprintf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
-        sqlite3_fprintf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
-        sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+        cli_printf(p->out, "vfs.zName      = \"%s\"\n", pVfs->zName);
+        cli_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
+        cli_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
+        cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
       }
     }
   }else
@@ -10671,13 +10910,13 @@ static int do_meta_command(const char *zLine, ShellState *p){
       sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
     }
     for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
-      sqlite3_fprintf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
+      cli_printf(p->out, "vfs.zName      = \"%s\"%s\n", pVfs->zName,
             pVfs==pCurrent ? "  <--- CURRENT" : "");
-      sqlite3_fprintf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
-      sqlite3_fprintf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
-      sqlite3_fprintf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+      cli_printf(p->out, "vfs.iVersion   = %d\n", pVfs->iVersion);
+      cli_printf(p->out, "vfs.szOsFile   = %d\n", pVfs->szOsFile);
+      cli_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
       if( pVfs->pNext ){
-        sqlite3_fputs("-----------------------------------\n", p->out);
+        cli_puts("-----------------------------------\n", p->out);
       }
     }
   }else
@@ -10688,7 +10927,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
     if( p->db ){
       sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
       if( zVfsName ){
-        sqlite3_fprintf(p->out, "%s\n", zVfsName);
+        cli_printf(p->out, "%s\n", zVfsName);
         sqlite3_free(zVfsName);
       }
     }
@@ -10714,7 +10953,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
   }else
 
   {
-    sqlite3_fprintf(stderr,"Error: unknown command or invalid arguments: "
+    cli_printf(stderr,"Error: unknown command or invalid arguments: "
           " \"%s\". Enter \".help\" for help\n", azArg[0]);
     rc = 1;
   }
@@ -10987,7 +11226,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
     }else{
       sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
     }
-    sqlite3_fprintf(stderr,"%s %s\n", zPrefix, zErrorTail);
+    cli_printf(stderr,"%s %s\n", zPrefix, zErrorTail);
     sqlite3_free(zErrMsg);
     zErrMsg = 0;
     return 1;
@@ -10996,7 +11235,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
     sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
             "changes: %lld   total_changes: %lld",
             sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
-    sqlite3_fprintf(p->out, "%s\n", zLineBuf);
+    cli_printf(p->out, "%s\n", zLineBuf);
   }
 
   if( doAutoDetectRestore(p, zSql) ) return 1;
@@ -11005,7 +11244,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
 
 static void echo_group_input(ShellState *p, const char *zDo){
   if( p->mode.bEcho ){
-    sqlite3_fprintf(p->out, "%s\n", zDo);
+    cli_printf(p->out, "%s\n", zDo);
     fflush(p->out);
   }
 }
@@ -11067,7 +11306,7 @@ static int process_input(ShellState *p, const char *zSrc){
 
   if( p->inputNesting==MAX_INPUT_NESTING ){
     /* This will be more informative in a later version. */
-    sqlite3_fprintf(stderr,"%s: Input nesting limit (%d) reached at line %lld."
+    cli_printf(stderr,"%s: Input nesting limit (%d) reached at line %lld."
           " Check recursion.\n", zSrc, MAX_INPUT_NESTING, p->lineno);
     return 1;
   }
@@ -11082,7 +11321,7 @@ static int process_input(ShellState *p, const char *zSrc){
     zLine = one_input_line(p->in, zLine, nSql>0);
     if( zLine==0 ){
       /* End of input */
-      if( p->in==0 && stdin_is_interactive ) sqlite3_fputs("\n", p->out);
+      if( p->in==0 && stdin_is_interactive ) cli_puts("\n", p->out);
       break;
     }
     if( seenInterrupt ){
@@ -11139,7 +11378,7 @@ static int process_input(ShellState *p, const char *zSrc){
     if( nSql>0x7fff0000 ){
       char zSize[100];
       sqlite3_snprintf(sizeof(zSize),zSize,"%,lld",nSql);
-      sqlite3_fprintf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n",
+      cli_printf(stderr, "%s:%lld: Input SQL is too big: %s bytes\n",
                       zSrc, startline, zSize);
       nSql = 0;
       errCnt++;
@@ -11334,13 +11573,13 @@ static void process_sqliterc(
   p->in = sqliterc ? sqlite3_fopen(sqliterc,"rb") : 0;
   if( p->in ){
     if( stdin_is_interactive ){
-      sqlite3_fprintf(stderr,"-- Loading resources from %s\n", sqliterc);
+      cli_printf(stderr,"-- Loading resources from %s\n", sqliterc);
     }
-    if( process_input(p, sqliterc) && bail_on_error ) exit(1);
+    if( process_input(p, sqliterc) && bail_on_error ) cli_exit(1);
     fclose(p->in);
   }else if( sqliterc_override!=0 ){
-    sqlite3_fprintf(stderr,"cannot open: \"%s\"\n", sqliterc);
-    if( bail_on_error ) exit(1);
+    cli_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
+    if( bail_on_error ) cli_exit(1);
   }
   p->in = inSaved;
   p->lineno = savedLineno;
@@ -11418,11 +11657,11 @@ static const char zOptions[] =
 #endif
 ;
 static void usage(int showDetail){
-  sqlite3_fprintf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n"
+  cli_printf(stderr,"Usage: %s [OPTIONS] [FILENAME [SQL...]]\n"
        "FILENAME is the name of an SQLite database. A new database is created\n"
        "if the file does not previously exist. Defaults to :memory:.\n", Argv0);
   if( showDetail ){
-    sqlite3_fprintf(stderr,"OPTIONS include:\n%s", zOptions);
+    cli_printf(stderr,"OPTIONS include:\n%s", zOptions);
   }else{
     eputz("Use the -help option for additional information\n");
   }
@@ -11478,7 +11717,7 @@ static void printBold(const char *zText){
 }
 #else
 static void printBold(const char *zText){
-  sqlite3_fprintf(stdout, "\033[1m%s\033[0m", zText);
+  cli_printf(stdout, "\033[1m%s\033[0m", zText);
 }
 #endif
 
@@ -11488,9 +11727,9 @@ static void printBold(const char *zText){
 */
 static char *cmdline_option_value(int argc, char **argv, int i){
   if( i==argc ){
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
             "%s: Error: missing argument to %s\n", argv[0], argv[argc-1]);
-    exit(1);
+    cli_exit(1);
   }
   return argv[i];
 }
@@ -11503,7 +11742,7 @@ static void sayAbnormalExit(void){
 */
 static int vfstraceOut(const char *z, void *pArg){
   ShellState *p = (ShellState*)pArg;
-  sqlite3_fputs(z, p->out);
+  cli_puts(z, p->out);
   fflush(p->out);
   return 1;
 }
@@ -11568,7 +11807,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   if( getenv("SQLITE_DEBUG_BREAK") ){
     if( isatty(0) && isatty(2) ){
       char zLine[100];
-      sqlite3_fprintf(stderr,
+      cli_printf(stderr,
             "attach debugger to process %d and press ENTER to continue...",
             GETPID());
       if( sqlite3_fgets(zLine, sizeof(zLine), stdin)!=0
@@ -11600,7 +11839,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 
 #if USE_SYSTEM_SQLITE+0!=1
   if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
           "SQLite header and source version mismatch\n%s\n%s\n",
           sqlite3_sourceid(), SQLITE_SOURCE_ID);
     exit(1);
@@ -11726,7 +11965,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
         int szHdr = 0;
         sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &szHdr);
         sz += szHdr;
-        sqlite3_fprintf(stdout, "Page cache size increased to %d to accommodate"
+        cli_printf(stdout, "Page cache size increased to %d to accommodate"
                         " the %d-byte headers\n", (int)sz, szHdr);
       }
       verify_uninitialized();
@@ -11841,7 +12080,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     if( pVfs ){
       sqlite3_vfs_register(pVfs, 1);
     }else{
-      sqlite3_fprintf(stderr,"no such VFS: \"%s\"\n", zVfs);
+      cli_printf(stderr,"no such VFS: \"%s\"\n", zVfs);
       exit(1);
     }
   }
@@ -11851,7 +12090,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     data.pAuxDb->zDbFilename = ":memory:";
     warnInmemoryDb = argc==1;
 #else
-    sqlite3_fprintf(stderr,
+    cli_printf(stderr,
                     "%s: Error: no database filename specified\n", Argv0);
     return 1;
 #endif
@@ -11924,12 +12163,12 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
         }
       }
       if( k>=ArraySize(qrfEscNames) ){
-        sqlite3_fprintf(stderr, "unknown control character escape mode \"%s\""
+        cli_printf(stderr, "unknown control character escape mode \"%s\""
                                 " - choices:", zEsc);
         for(k=0; k<ArraySize(qrfEscNames); k++){
-          sqlite3_fprintf(stderr, " %s", qrfEscNames[k]);
+          cli_printf(stderr, " %s", qrfEscNames[k]);
         }
-        sqlite3_fprintf(stderr, "\n");
+        cli_printf(stderr, "\n");
         exit(1);
       }
 #ifdef SQLITE_HAVE_ZLIB
@@ -11995,7 +12234,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     }else if( cli_strcmp(z,"-bail")==0 ){
       /* No-op.  The bail_on_error flag should already be set. */
     }else if( cli_strcmp(z,"-version")==0 ){
-      sqlite3_fprintf(stdout, "%s %s (%d-bit)\n",
+      cli_printf(stdout, "%s %s (%d-bit)\n",
             sqlite3_libversion(), sqlite3_sourceid(), 8*(int)sizeof(char*));
       return 0;
     }else if( cli_strcmp(z,"-interactive")==0 ){
@@ -12059,14 +12298,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
           sqlite3_free(zErrMsg);
           if( bail_on_error ) return rc!=0 ? rc : 1;
         }else if( rc!=0 ){
-          sqlite3_fprintf(stderr,"Error: unable to process SQL \"%s\"\n", z);
+          cli_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
           if( bail_on_error ) return rc;
         }
       }
 #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
     }else if( cli_strncmp(z, "-A", 2)==0 ){
       if( nCmd>0 ){
-        sqlite3_fprintf(stderr,"Error: cannot mix regular SQL or dot-commands"
+        cli_printf(stderr,"Error: cannot mix regular SQL or dot-commands"
               " with \"%s\"\n", z);
         return 1;
       }
@@ -12085,7 +12324,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
       /* Acted upon in first pass. */
     }else{
-      sqlite3_fprintf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
+      cli_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
       eputz("Use -help for a list of options.\n");
       return 1;
     }
@@ -12112,7 +12351,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
           if( zErrMsg!=0 ){
             shellEmitError(zErrMsg);
           }else{
-            sqlite3_fprintf(stderr,
+            cli_printf(stderr,
                             "Error: unable to process SQL: %s\n", azCmd[i]);
           }
           sqlite3_free(zErrMsg);
@@ -12127,7 +12366,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     if( stdin_is_interactive ){
       char *zHome;
       char *zHistory;
-      sqlite3_fprintf(stdout,
+      cli_printf(stdout,
             "SQLite version %s %.19s\n" /*extra-version-info*/
             "Enter \".help\" for usage hints.\n",
             sqlite3_libversion(), sqlite3_sourceid());
@@ -12202,9 +12441,16 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 #endif
   modeFree(&data.mode);
   free(data.zNonce);
+  free(data.dot.zCopy);
   free(data.dot.azArg);
   free(data.dot.aiOfst);
   free(data.dot.abQuot);
+  if( data.nTestRun ){
+    sqlite3_fprintf(stderr, "%d test%s run with %d error%s\n",
+       data.nTestRun, data.nTestRun==1 ? "" : "s",
+       data.nTestErr, data.nTestErr==1 ? "" : "s");
+    rc = data.nTestErr>0;
+  }
   /* Clear the global data structure so that valgrind will detect memory
   ** leaks */
   memset(&data, 0, sizeof(data));
@@ -12213,7 +12459,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   }
 #ifdef SQLITE_DEBUG
   if( sqlite3_memory_used()>mem_main_enter ){
-    sqlite3_fprintf(stderr,"Memory leaked: %u bytes\n",
+    cli_printf(stderr,"Memory leaked: %u bytes\n",
           (unsigned int)(sqlite3_memory_used()-mem_main_enter));
   }
 #endif
@@ -12253,7 +12499,7 @@ sqlite3_vfs * fiddle_db_vfs(const char *zDbName){
 
 /* Only for emcc experimentation purposes. */
 sqlite3 * fiddle_db_arg(sqlite3 *arg){
-    sqlite3_fprintf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg);
+    cli_printf(stdout, "fiddle_db_arg(%p)\n", (const void*)arg);
     return arg;
 }
 
@@ -12290,7 +12536,7 @@ void fiddle_reset_db(void){
       ** Resolve problem reported in
       ** https://sqlite.org/forum/forumpost/0b41a25d65
       */
-      sqlite3_fputs("Rolling back in-progress transaction.\n", stdout);
+      cli_puts("Rolling back in-progress transaction.\n", stdout);
       sqlite3_exec(globalDb,"ROLLBACK", 0, 0, 0);
     }
     rc = sqlite3_db_config(globalDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
index 8f20fabaa171349555001181795af54614cb941c..95b44ffd898f3269e19183ec2008976992160ab1 100644 (file)
@@ -511,17 +511,8 @@ SELECT readfile('FOO');"
 do_test shell1-3.15.3 {
   # too many arguments
   catchcmd "test.db" ".output FOO BAD"
-} {1 {ERROR: extra parameter: "BAD".  Usage:
-.output ?FILE?           Send output to FILE or stdout if FILE is omitted
-   If FILE begins with '|' then open it as a pipe.
-   If FILE is 'off' then output is disabled.
-   Options:
-     --bom                 Prefix output with a UTF8 byte-order mark
-     -e                    Send output to the system text editor
-     --plain               Use text/plain for -w option
-     -w                    Send output to a web browser
-     -x                    Send output as CSV to a spreadsheet
-child process exited abnormally}}
+} {1 {line 1: .output FOO BAD
+line 1:             ^--- surplus argument}}
 
 # .output stdout         Send output to the screen
 do_test shell1-3.16.1 {
@@ -530,17 +521,8 @@ do_test shell1-3.16.1 {
 do_test shell1-3.16.2 {
   # too many arguments
   catchcmd "test.db" ".output stdout BAD"
-} {1 {ERROR: extra parameter: "BAD".  Usage:
-.output ?FILE?           Send output to FILE or stdout if FILE is omitted
-   If FILE begins with '|' then open it as a pipe.
-   If FILE is 'off' then output is disabled.
-   Options:
-     --bom                 Prefix output with a UTF8 byte-order mark
-     -e                    Send output to the system text editor
-     --plain               Use text/plain for -w option
-     -w                    Send output to a web browser
-     -x                    Send output as CSV to a spreadsheet
-child process exited abnormally}}
+} {1 {line 1: .output stdout BAD
+line 1:                ^--- surplus argument}}
 
 # .prompt MAIN CONTINUE  Replace the standard prompts
 do_test shell1-3.17.1 {