diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index db314c326fd104708d3654d983087b050d6ab3e7..29ad1aa275a6c4bca8545d78a2cb7eacc69cf74a 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -277,7 +277,8 @@ EOF
       <term><option>--no-readline</></term>
       <listitem>
       <para>
-       Do not use <application>readline</application> for line editing and do not use the history.
+       Do not use <application>Readline</application> for line editing and do
+       not use the command history.
        This can be useful to turn off tab expansion when cutting and pasting.
       </para>
       </listitem>
@@ -2357,12 +2358,13 @@ lo_import 152801
         <term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
         <listitem>
         <para>
-        Print or save the command line history to <replaceable
-        class="parameter">filename</replaceable>. If <replaceable
-        class="parameter">filename</replaceable> is omitted, the history
-        is written to the standard output. This option is only available
-        if <application>psql</application> is configured to use the
-        <acronym>GNU</acronym> <application>Readline</application> library.
+        Print <application>psql</application>'s command line history
+        to <replaceable class="parameter">filename</replaceable>.
+        If <replaceable class="parameter">filename</replaceable> is omitted,
+        the history is written to the standard output (using the pager if
+        appropriate).  This command is not available
+        if <application>psql</application> was built
+        without <application>Readline</application> support.
         </para>
         </listitem>
       </varlistentry>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a66093abb3a9804da4ccb753cd6fdac0678245d0..39b5777bcb2a46a6faf25798c41385bd3b834910 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1088,20 +1088,8 @@ exec_command(const char *cmd,
 		char	   *fname = psql_scan_slash_option(scan_state,
 												   OT_NORMAL, NULL, true);
 
-#if defined(WIN32) && !defined(__CYGWIN__)
-
-		/*
-		 * XXX This does not work for all terminal environments or for output
-		 * containing non-ASCII characters; see comments in simple_prompt().
-		 */
-#define DEVTTY	"con"
-#else
-#define DEVTTY	"/dev/tty"
-#endif
-
 		expand_tilde(&fname);
-		/* This scrolls off the screen when using /dev/tty */
-		success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
+		success = printHistory(fname, pset.popt.topt.pager);
 		if (success && !pset.quiet && fname)
 			printf(_("Wrote history to file \"%s\".\n"), fname);
 		if (!fname)
diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index aa32a3f5c163d7290ecf197f2d08b47a0718bb13..6416ab91c2e022acaedd4565da8bda47703a6177 100644
--- a/src/bin/psql/input.c
+++ b/src/bin/psql/input.c
@@ -11,6 +11,7 @@
 #include <unistd.h>
 #endif
 #include <fcntl.h>
+#include <limits.h>
 
 #include "input.h"
 #include "settings.h"
@@ -222,23 +223,73 @@ gets_fromFile(FILE *source)
 
 
 #ifdef USE_READLINE
+
+/*
+ * Macros to iterate over each element of the history list in order
+ *
+ * You would think this would be simple enough, but in its inimitable fashion
+ * libedit has managed to break it: in libreadline we must use next_history()
+ * to go from oldest to newest, but in libedit we must use previous_history().
+ * To detect what to do, we make a trial call of previous_history(): if it
+ * fails, then either next_history() is what to use, or there's zero or one
+ * history entry so that it doesn't matter which direction we go.
+ *
+ * In case that wasn't disgusting enough: the code below is not as obvious as
+ * it might appear.  In some libedit releases history_set_pos(0) fails until
+ * at least one add_history() call has been done.  This is not an issue for
+ * printHistory() or encode_history(), which cannot be invoked before that has
+ * happened.  In decode_history(), that's not so, and what actually happens is
+ * that we are sitting on the newest entry to start with, previous_history()
+ * fails, and we iterate over all the entries using next_history().  So the
+ * decode_history() loop iterates over the entries in the wrong order when
+ * using such a libedit release, and if there were another attempt to use
+ * BEGIN_ITERATE_HISTORY() before some add_history() call had happened, it
+ * wouldn't work.  Fortunately we don't care about either of those things.
+ *
+ * Usage pattern is:
+ *
+ *		BEGIN_ITERATE_HISTORY(varname);
+ *		{
+ *			loop body referencing varname->line;
+ *		}
+ *		END_ITERATE_HISTORY();
+ */
+#define BEGIN_ITERATE_HISTORY(VARNAME) \
+	do { \
+		HIST_ENTRY *VARNAME; \
+		bool		use_prev_; \
+		\
+		history_set_pos(0); \
+		use_prev_ = (previous_history() != NULL); \
+		history_set_pos(0); \
+		for (VARNAME = current_history(); VARNAME != NULL; \
+			 VARNAME = use_prev_ ? previous_history() : next_history()) \
+		{ \
+			(void) 0
+
+#define END_ITERATE_HISTORY() \
+		} \
+	} while(0)
+
+
 /*
  * Convert newlines to NL_IN_HISTORY for safe saving in readline history file
  */
 static void
 encode_history(void)
 {
-	HIST_ENTRY *cur_hist;
-	char	   *cur_ptr;
-
-	history_set_pos(0);
-	for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+	BEGIN_ITERATE_HISTORY(cur_hist);
 	{
+		char	   *cur_ptr;
+
 		/* some platforms declare HIST_ENTRY.line as const char * */
 		for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+		{
 			if (*cur_ptr == '\n')
 				*cur_ptr = NL_IN_HISTORY;
+		}
 	}
+	END_ITERATE_HISTORY();
 }
 
 /*
@@ -247,17 +298,18 @@ encode_history(void)
 static void
 decode_history(void)
 {
-	HIST_ENTRY *cur_hist;
-	char	   *cur_ptr;
-
-	history_set_pos(0);
-	for (cur_hist = current_history(); cur_hist; cur_hist = next_history())
+	BEGIN_ITERATE_HISTORY(cur_hist);
 	{
+		char	   *cur_ptr;
+
 		/* some platforms declare HIST_ENTRY.line as const char * */
 		for (cur_ptr = (char *) cur_hist->line; *cur_ptr; cur_ptr++)
+		{
 			if (*cur_ptr == NL_IN_HISTORY)
 				*cur_ptr = '\n';
+		}
 	}
+	END_ITERATE_HISTORY();
 }
 #endif   /* USE_READLINE */
 
@@ -319,22 +371,15 @@ initializeInput(int flags)
 
 
 /*
- * This function saves the readline history when user
- * runs \s command or when psql exits.
+ * This function saves the readline history when psql exits.
  *
  * fname: pathname of history file.  (Should really be "const char *",
  * but some ancient versions of readline omit the const-decoration.)
  *
  * max_lines: if >= 0, limit history file to that many entries.
- *
- * appendFlag: if true, try to append just our new lines to the file.
- * If false, write the whole available history.
- *
- * encodeFlag: whether to encode \n as \x01.  For \s calls we don't wish
- * to do that, but must do so when saving the final history file.
  */
-bool
-saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
+static bool
+saveHistory(char *fname, int max_lines)
 {
 #ifdef USE_READLINE
 
@@ -344,11 +389,15 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
 	 * where write_history will fail because it tries to chmod the target
 	 * file.
 	 */
-	if (useHistory && fname &&
-		strcmp(fname, DEVNULL) != 0)
+	if (strcmp(fname, DEVNULL) != 0)
 	{
-		if (encodeFlag)
-			encode_history();
+		/*
+		 * Encode \n, since otherwise readline will reload multiline history
+		 * entries as separate lines.  (libedit doesn't really need this, but
+		 * we do it anyway since it's too hard to tell which implementation we
+		 * are using.)
+		 */
+		encode_history();
 
 		/*
 		 * On newer versions of libreadline, truncate the history file as
@@ -362,7 +411,6 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
 		 * see if the write failed.  Similarly for append_history.
 		 */
 #if defined(HAVE_HISTORY_TRUNCATE_FILE) && defined(HAVE_APPEND_HISTORY)
-		if (appendFlag)
 		{
 			int			nlines;
 			int			fd;
@@ -387,8 +435,7 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
 			if (errno == 0)
 				return true;
 		}
-		else
-#endif
+#else							/* don't have append support */
 		{
 			/* truncate what we have ... */
 			if (max_lines >= 0)
@@ -399,19 +446,73 @@ saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag)
 			if (errno == 0)
 				return true;
 		}
+#endif
 
 		psql_error("could not save history to file \"%s\": %s\n",
 				   fname, strerror(errno));
 	}
-#else
-	/* only get here in \s case, so complain */
-	psql_error("history is not supported by this installation\n");
 #endif
 
 	return false;
 }
 
 
+/*
+ * Print history to the specified file, or to the console if fname is NULL
+ * (psql \s command)
+ *
+ * We used to use saveHistory() for this purpose, but that doesn't permit
+ * use of a pager; moreover libedit's implementation behaves incompatibly
+ * (preferring to encode its output) and may fail outright when the target
+ * file is specified as /dev/tty.
+ */
+bool
+printHistory(const char *fname, unsigned short int pager)
+{
+#ifdef USE_READLINE
+	FILE	   *output;
+	bool		is_pager;
+
+	if (!useHistory)
+		return false;
+
+	if (fname == NULL)
+	{
+		/* use pager, if enabled, when printing to console */
+		output = PageOutput(INT_MAX, pager);
+		is_pager = true;
+	}
+	else
+	{
+		output = fopen(fname, "w");
+		if (output == NULL)
+		{
+			psql_error("could not save history to file \"%s\": %s\n",
+					   fname, strerror(errno));
+			return false;
+		}
+		is_pager = false;
+	}
+
+	BEGIN_ITERATE_HISTORY(cur_hist);
+	{
+		fprintf(output, "%s\n", cur_hist->line);
+	}
+	END_ITERATE_HISTORY();
+
+	if (is_pager)
+		ClosePager(output);
+	else
+		fclose(output);
+
+	return true;
+#else
+	psql_error("history is not supported by this installation\n");
+	return false;
+#endif
+}
+
+
 static void
 finishInput(void)
 {
@@ -421,7 +522,7 @@ finishInput(void)
 		int			hist_size;
 
 		hist_size = GetVariableNum(pset.vars, "HISTSIZE", 500, -1, true);
-		saveHistory(psql_history, hist_size, true, true);
+		(void) saveHistory(psql_history, hist_size);
 		free(psql_history);
 		psql_history = NULL;
 	}
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index 1d10a22856c97a4f58d33793e1f516be6acfb4b5..1b2239974acb1a21ce49fb859d7f6af3c8f9c361 100644
--- a/src/bin/psql/input.h
+++ b/src/bin/psql/input.h
@@ -42,7 +42,8 @@ char	   *gets_interactive(const char *prompt);
 char	   *gets_fromFile(FILE *source);
 
 void		initializeInput(int flags);
-bool		saveHistory(char *fname, int max_lines, bool appendFlag, bool encodeFlag);
+
+bool		printHistory(const char *fname, unsigned short int pager);
 
 void		pg_append_history(const char *s, PQExpBuffer history_buf);
 void		pg_send_history(PQExpBuffer history_buf);