Skip to content

set_key does not handle single quote ' inside value properly #543

Open

Description

@YuryHrytsuk

Problem

set_key function does not properly handle single quote ' inside value.

python-dotenv version: 1.0.1

Related PR

How to reproduce

  1. Run python script below
  2. Run source .env

Python Script

from dotenv import set_key

def main():
    var_value = "I'm a bug :("
    set_key(".env", "VAR", var_value)


if __name__ == "__main__":
    main()

Error from source .env

bash: .env: line 1: syntax error near unexpected token `('
bash: .env: line 1: `VAR='I\'m a bug :(''

Example of fix

⚠️ this fix produces values that dotenv cannot parse (see this issue) ⚠️

from dotenv import set_key

def fixed_set_key(file_path, key, value):
    # properly escape ' if present
    # https://stackoverflow.com/questions/8254120/how-can-i-escape-a-single-quote-in-a-single-quote-string-in-bash/26165123#26165123
    value = value.replace("'", "'\"'\"'")
    
    set_key(file_path, key, f"'{value}'", quote_mode="never")

def main():
    var_value = "I'm a fixed :)"
    fixed_set_key(".env", "VAR", var_value)


if __name__ == "__main__":
    main()

Activity

  1. theskumar commented on Aug 16, 2026

    @theskumar
    Owner

    Grouping the shell-quoting work here so it's easy to track:

    #670 makes the export=True path shell-safe - for a value like this it writes export VAR="I'm a bug :(", which both source and dotenv read back correctly. So the export case is covered there.

    The exact repro in this issue is the default (non-export) path though, and that's still open: set_key(".env", "VAR", "I'm a bug :(") writes VAR='I\'m a bug :(', which dotenv parses back fine but bash can't source (a \' inside single quotes isn't valid in bash). Worth deciding separately whether non-export output should be POSIX-sourceable at all, since that's a slightly different contract from "dotenv can round-trip it".

    Related: #544 is the parser side of the same theme (reading a hand-written 'I'\''m' bash-concat value), and #670 doesn't touch the parser, so that one stands on its own too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions