diff --git a/CHANGELOG.md b/CHANGELOG.md index fa81cc6e..2aa68d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed - Strip a leading UTF-8 BOM from `.env` file contents so the first variable is no longer silently lost when the file is saved with BOM (e.g. by some JetBrains IDEs on Windows) by [@h1whelan] in [#640] +- `set_key` now writes values containing `'` using a double-quoted, escaped form so the resulting `.env` line is valid shell and round-trips through the parser (#543) ## [1.2.2] - 2026-03-01 diff --git a/src/dotenv/main.py b/src/dotenv/main.py index 3c4608d5..c83d7d3a 100644 --- a/src/dotenv/main.py +++ b/src/dotenv/main.py @@ -216,7 +216,12 @@ def set_key( ) if quote: - value_out = "'{}'".format(value_to_set.replace("'", "\\'")) + if "'" in value_to_set: + # a single quote can't be escaped in a single-quoted value; use double quotes + escaped = value_to_set.replace("\\", "\\\\").replace('"', '\\"') + value_out = '"{}"'.format(escaped) + else: + value_out = "'{}'".format(value_to_set) else: value_out = value_to_set if export: diff --git a/tests/test_main.py b/tests/test_main.py index 1c33c808..b0d71886 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -29,10 +29,11 @@ def test_set_key_no_file(tmp_path): [ ("", "a", "", (True, "a", ""), "a=''\n"), ("", "a", "b", (True, "a", "b"), "a='b'\n"), - ("", "a", "'b'", (True, "a", "'b'"), "a='\\'b\\''\n"), + ("", "a", "'b'", (True, "a", "'b'"), "a=\"'b'\"\n"), ("", "a", '"b"', (True, "a", '"b"'), "a='\"b\"'\n"), - ("", "a", "b'c", (True, "a", "b'c"), "a='b\\'c'\n"), + ("", "a", "b'c", (True, "a", "b'c"), 'a="b\'c"\n'), ("", "a", 'b"c', (True, "a", 'b"c'), "a='b\"c'\n"), + ("", "a", 'I\'m "in"', (True, "a", 'I\'m "in"'), 'a="I\'m \\"in\\""\n'), ("a=b", "a", "c", (True, "a", "c"), "a='c'\n"), ("a=b\n", "a", "c", (True, "a", "c"), "a='c'\n"), ("a=b\n\n", "a", "c", (True, "a", "c"), "a='c'\n\n"),