[PATCH v2 2/2] scripts/config: Use in-place editing (-i) in sed portably

Rong Zhang <[email protected]> Wed, 22 Jul 2026 01:50:49 +0800
Newsgroups org.kernel.vger.linux-kbuild,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
The use of in-place editing was removed by commit 83e8b90e1d2c
("scripts/config: use sed's POSIX interface").

Before that, the script used bare `-i' to skip creating a backup file.
In fact, major sed implementations have supported `-i' for over a
decade. It's really doubtful if anyone would still build Linux on a Unix
system without it. The issue is more about how we use it:

FreeBSD and macOS disallow bare `-i'. To skip creating a backup, an
empty string ("zero-length extension") must be passed as a separate
argument following `-i'.

GNU and other BSDs accept bare `-i' to skip creating a backup, but
disallow passing a zero-length extension.

That being said, when thinking about it optimistically, using `-i' is
portable as long as a backup is created.

Use in-place editing (-i) in a portable manner by creating a backup file
with a .swp extension (the same name as the current temporary file). The
backup file will be deleted on exit.

A rough benchmark with ~1000 editions showed a 14.4% speedup (5.27s =>
4.51s, GNU sed). The FreeBSD sed showed a similar speedup.

Reviewed-by: Nicolas Schier <[email protected]>
Signed-off-by: Rong Zhang <[email protected]>
---
Changes in v2:
- Fix a typo in commit message (thanks Nicolas Schier)
- Do not delete .swp file when sed is not invoked, e.g., `--state'
  - In my test, unnecessarily deleting .swp file makes `--state' 10%
    slower due to the overhead of extra fork() and execve()
  - Unnecessarily deleting .swp file may corrupt Vim even if no editing
    command is passed
---
 scripts/config | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/scripts/config b/scripts/config
index 1f290372a4ce..7c083c860341 100755
--- a/scripts/config
+++ b/scripts/config
@@ -71,35 +71,34 @@ txt_append() {
 	local anchor="$1"
 	local insert="$2"
 	local infile="$3"
-	local tmpfile="$infile.swp"
 
 	# sed append cmd: 'a\' + newline + text + newline
 	cmd="$(printf "a\\%b$insert" "\n")"
 
-	sed -E -e "/$anchor/$cmd" "$infile" >"$tmpfile"
-	# replace original file with the edited one
-	mv "$tmpfile" "$infile"
+	# We don't really need a backup file, but in-place editing with backup
+	# skipped is not portable due to different implementations parsing
+	# arguments in incompatible manners.
+	# Create a backup file anyway to ensure portability. The file will be
+	# deleted on exit.
+	sed -E -i.swp -e "/$anchor/$cmd" "$infile"
+	SED_EDITED=1
 }
 
 txt_subst() {
 	local before="$1"
 	local after="$2"
 	local infile="$3"
-	local tmpfile="$infile.swp"
 
-	sed -E -e "s$SED_DELIM$before$SED_DELIM$after$SED_DELIM" "$infile" >"$tmpfile"
-	# replace original file with the edited one
-	mv "$tmpfile" "$infile"
+	sed -E -i.swp -e "s$SED_DELIM$before$SED_DELIM$after$SED_DELIM" "$infile"
+	SED_EDITED=1
 }
 
 txt_delete() {
 	local text="$1"
 	local infile="$2"
-	local tmpfile="$infile.swp"
 
-	sed -E -e "/$text/d" "$infile" >"$tmpfile"
-	# replace original file with the edited one
-	mv "$tmpfile" "$infile"
+	sed -E -i.swp -e "/$text/d" "$infile"
+	SED_EDITED=1
 }
 
 set_var() {
@@ -122,6 +121,14 @@ undef_var() {
 	txt_delete "^($name=|# $name is not set)" "$FN"
 }
 
+SED_EDITED=0
+on_exit() {
+	if [ "$SED_EDITED" -ge 1 ]; then
+		rm -f "$FN.swp"
+	fi
+}
+trap on_exit EXIT
+
 FN=.config
 CMDS=()
 while [[ $# -gt 0 ]]; do

-- 
2.53.0