Subject: Quoting of *_command arguments

From: Bruce Momjian <[email-masked]>
Date: Tue, 11 Nov 2025 14:31:21 -0500

We inconsistently double-quote the "%f" and "%p" arguments of
"archive_command" and "restore_command".  Paths with spaces or special
characters, especially directory names in these cases, would need
double-quotes.

This patch adds double-quotes to all instances.  I can also go the other
way and remove them all, but we should be consistent.

-- 
  Bruce Momjian  <[email-masked]>        https://momjian.us
  EDB                                      https://enterprisedb.com

  Do not let urgent matters crowd out time for investment in the future.
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 5f7489afbd1..168444eccc5 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -627,7 +627,7 @@ tar -cf backup.tar /usr/local/pgsql/data
     character in the command.  The simplest useful command is something
     like:
 <programlisting>
-archive_command = 'test ! -f /mnt/server/archivedir/%f &amp;&amp; cp %p /mnt/server/archivedir/%f'  # Unix
+archive_command = 'test ! -f "/mnt/server/archivedir/%f" &amp;&amp; cp "%p" "/mnt/server/archivedir/%f"'  # Unix
 archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows
 </programlisting>
     which will copy archivable WAL segments to the directory
@@ -1294,7 +1294,7 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
     character in the command.  The simplest useful command is
     something like:
 <programlisting>
-restore_command = 'cp /mnt/server/archivedir/%f %p'
+restore_command = 'cp "/mnt/server/archivedir/%f" "%p"'
 </programlisting>
     which will copy previously archived WAL segments from the directory
     <filename>/mnt/server/archivedir</filename>.  Of course, you can use something
@@ -1493,11 +1493,11 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
       If archive storage size is a concern, you can use
       <application>gzip</application> to compress the archive files:
 <programlisting>
-archive_command = 'gzip &lt; %p &gt; /mnt/server/archivedir/%f.gz'
+archive_command = 'gzip &lt; "%p" &gt; "/mnt/server/archivedir/%f.gz"'
 </programlisting>
       You will then need to use <application>gunzip</application> during recovery:
 <programlisting>
-restore_command = 'gunzip &lt; /mnt/server/archivedir/%f.gz &gt; %p'
+restore_command = 'gunzip &lt; "/mnt/server/archivedir/%f.gz" &gt; "%p"'
 </programlisting>
      </para>
     </sect3>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 683f7c36f46..bd925e027b9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4059,7 +4059,7 @@ include_dir 'conf.d'
         names that are not present in the archive; it must return nonzero
         when so asked.  Examples:
 <programlisting>
-restore_command = 'cp /mnt/server/archivedir/%f "%p"'
+restore_command = 'cp "/mnt/server/archivedir/%f" "%p"'
 restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
 </programlisting>
         An exception is that if the command was terminated by a signal (other
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 742deb037b7..ade5995dd7a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -745,7 +745,7 @@ protocol to make nodes agree on a serializable transactional order.
     A simple example of configuration is:
 <programlisting>
 primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
-restore_command = 'cp /path/to/archive/%f %p'
+restore_command = 'cp "/path/to/archive/%f" "%p"'
 archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'
 </programlisting>
    </para>
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index f62b61967ef..0161ad2feda 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -283,7 +283,7 @@
 #archive_command = ''		# command to use to archive a WAL file
 				# placeholders: %p = path of file to archive
 				#               %f = file name only
-				# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
+				# e.g. 'test ! -f "/mnt/server/archivedir/%f" && cp "%p" "/mnt/server/archivedir/%f"'
 #archive_timeout = 0		# force a WAL file switch after this
 				# number of seconds; 0 disables
 
@@ -294,7 +294,7 @@
 #restore_command = ''		# command to use to restore an archived WAL file
 				# placeholders: %p = path of file to restore
 				#               %f = file name only
-				# e.g. 'cp /mnt/server/archivedir/%f %p'
+				# e.g. 'cp "/mnt/server/archivedir/%f" "%p"'
 #archive_cleanup_command = ''	# command to execute at every restartpoint
 #recovery_end_command = ''	# command to execute at completion of recovery

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 12 Nov 2025 14:45:16 +0100

> way and remove them all, but we should be consistent.

I think %f doesn't need to be quoted because it will only contain 
certain characters, but if we tried to clarify that it would probably be 
too confusing.  It makes sense to nudge people that some quoting is prudent.

Your patch looks good to me.

---

From: Bruce Momjian <[email-masked]>
Date: Wed, 12 Nov 2025 08:48:39 -0500

On Wed, Nov 12, 2025 at 02:45:16PM +0100, Peter Eisentraut wrote:
> I think %f doesn't need to be quoted because it will only contain certain
> characters, but if we tried to clarify that it would probably be too
> confusing.  It makes sense to nudge people that some quoting is prudent.

Yeah, I did consider your point but you are right --- it is too complex
to quote one and not the other and expect people to understand why.

> Your patch looks good to me.

Thanks.

---

From: Bruce Momjian <[email-masked]>
Date: Fri, 14 Nov 2025 09:09:45 -0500

> > Your patch looks good to me.

Applied, and I added double-quotes for "%r".