diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 5262be4958907b4c2b3e3699b95a0f0520aa9d35..b903bc11dd9c61493b28a5e25ef40153c1b2697e 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1021,10 +1021,24 @@ testdb=>
 
         <tip>
         <para>
-        This operation is not as efficient as the <acronym>SQL</acronym>
-        <command>COPY</command> command because all data must pass
-        through the client/server connection. For large
-        amounts of data the <acronym>SQL</acronym> command might be preferable.
+        Another way to obtain the same result as <literal>\copy
+        ... to</literal> is to use the <acronym>SQL</acronym> <literal>COPY
+        ... TO STDOUT</literal> command and terminate it
+        with <literal>\g <replaceable>filename</replaceable></literal>
+        or <literal>\g |<replaceable>program</replaceable></literal>.
+        Unlike <literal>\copy</literal>, this method allows the command to
+        span multiple lines; also, variable interpolation and backquote
+        expansion can be used.
+        </para>
+        </tip>
+
+        <tip>
+        <para>
+        These operations are not as efficient as the <acronym>SQL</acronym>
+        <command>COPY</command> command with a file or program data source or
+        destination, because all data must pass through the client/server
+        connection.  For large amounts of data the <acronym>SQL</acronym>
+        command might be preferable.
         </para>
         </tip>
 
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index d257be5700fde2e8d872002fbc7b92abb22b20fd..f61082b9d308f28f6b8ec6f08535fcf65a452663 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1044,20 +1044,49 @@ ProcessResult(PGresult **results)
 			 * connection out of its COPY state, then call PQresultStatus()
 			 * once and report any error.
 			 *
-			 * If pset.copyStream is set, use that as data source/sink,
-			 * otherwise use queryFout or cur_cmd_source as appropriate.
+			 * For COPY OUT, direct the output to pset.copyStream if it's set,
+			 * otherwise to pset.gfname if it's set, otherwise to queryFout.
+			 * For COPY IN, use pset.copyStream as data source if it's set,
+			 * otherwise cur_cmd_source.
 			 */
-			FILE	   *copystream = pset.copyStream;
+			FILE	   *copystream;
 			PGresult   *copy_result;
 
 			SetCancelConn();
 			if (result_status == PGRES_COPY_OUT)
 			{
-				if (!copystream)
+				bool		need_close = false;
+				bool		is_pipe = false;
+
+				if (pset.copyStream)
+				{
+					/* invoked by \copy */
+					copystream = pset.copyStream;
+				}
+				else if (pset.gfname)
+				{
+					/* invoked by \g */
+					if (openQueryOutputFile(pset.gfname,
+											&copystream, &is_pipe))
+					{
+						need_close = true;
+						if (is_pipe)
+							disable_sigpipe_trap();
+					}
+					else
+						copystream = NULL;	/* discard COPY data entirely */
+				}
+				else
+				{
+					/* fall back to the generic query output stream */
 					copystream = pset.queryFout;
+				}
+
 				success = handleCopyOut(pset.db,
 										copystream,
-										&copy_result) && success;
+										&copy_result)
+					&& success
+					&& (copystream != NULL);
 
 				/*
 				 * Suppress status printing if the report would go to the same
@@ -1069,11 +1098,25 @@ ProcessResult(PGresult **results)
 					PQclear(copy_result);
 					copy_result = NULL;
 				}
+
+				if (need_close)
+				{
+					/* close \g argument file/pipe */
+					if (is_pipe)
+					{
+						pclose(copystream);
+						restore_sigpipe_trap();
+					}
+					else
+					{
+						fclose(copystream);
+					}
+				}
 			}
 			else
 			{
-				if (!copystream)
-					copystream = pset.cur_cmd_source;
+				/* COPY IN */
+				copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
 				success = handleCopyIn(pset.db,
 									   copystream,
 									   PQbinaryTuples(*results),
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 724ea9211a7d34291336e6e9220a472a55a89f55..e7cbbba9a73de6e294ff8db36ac0d90557028e4c 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -425,7 +425,10 @@ do_copy(const char *args)
  *
  * conn should be a database connection that you just issued COPY TO on
  * and got back a PGRES_COPY_OUT result.
+ *
  * copystream is the file stream for the data to go to.
+ * copystream can be NULL to eat the data without writing it anywhere.
+ *
  * The final status for the COPY is returned into *res (but note
  * we already reported the error, if it's not a success result).
  *
@@ -447,7 +450,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
 
 		if (buf)
 		{
-			if (OK && fwrite(buf, 1, ret, copystream) != ret)
+			if (OK && copystream && fwrite(buf, 1, ret, copystream) != ret)
 			{
 				psql_error("could not write COPY data: %s\n",
 						   strerror(errno));
@@ -458,7 +461,7 @@ handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
 		}
 	}
 
-	if (OK && fflush(copystream))
+	if (OK && copystream && fflush(copystream))
 	{
 		psql_error("could not write COPY data: %s\n",
 				   strerror(errno));