Best way to decode command line inputs to Unicode Python 2.7 scripts

前端 未结 2 1427
野趣味
野趣味 2021-01-07 13:16

All my scripts use Unicode literals throughout, with

from __future__ import unicode_literals

but this creates a problem when there is the p

2条回答
  •  鱼传尺愫
    2021-01-07 14:06

    sys.getfilesystemencoding() is the correct(but see examples) encoding for OS data such as filenames, environment variables, and command-line arguments.

    You could see the logic behind the choice: sys.argv[0] may be the path to the script (the filename) and therefore it is natural to assume that it uses the same encoding as other filenames and that other items in the argv list use the same character encoding as sys.argv[0]. os.environ['PATH'] contains paths and therefore it is also natural that environment variables use the same encoding:

    $ echo 'import sys; print(sys.argv)' >print_argv.py
    $ python print_argv.py
    ['print_argv.py']
    

    Note: sys.argv[0] is the script filename whatever other command-line arguments you might have.

    "best way" depends on your specific use-case e.g., on Windows, you should probably use Unicode API directly (CommandLineToArgvW()). On POSIX, if all you need is to pass some argv items to OS functions back (such as os.listdir()) then you could leave them as bytes -- command-line argument can be arbitrary byte sequence, see PEP 0383 -- Non-decodable Bytes in System Character Interfaces:

    import os, sys
    
    os.execl(sys.executable, sys.executable, '-c', 'import sys; print(sys.argv)',
             bytes(bytearray(range(1, 0x100))))
    

    As you can see POSIX allows to pass any bytes (except zero).

    Obviously, you can also misconfigure your environment:

    $ LANG=C PYTHONIOENCODING=latin-1 python -c'import sys;
    >   print(sys.argv, sys.stdin.encoding, sys.getfilesystemencoding())' €
    (['-c', '\xe2\x82\xac'], 'latin-1', 'ANSI_X3.4-1968') # Linux output
    

    The output shows that is encoded using utf-8 but both locale and PYTHONIOENCODING are configured differently.

    The examples demonstrate that sys.argv may be encoded using a character encoding that does not correspond to any of the standard encodings or it even may contain arbitrary (except zero byte) binary data on POSIX (no character encoding). On Windows, I guess, you could paste a Unicode string that can't be encoded using ANSI or OEM Windows encodings but you might get the correct value using Unicode API anyway (Python 2 probably drops data here).

    Python 3 uses Unicode sys.argv and therefore it shouldn't lose data on Windows (Unicode API is used) and it allows to demonstrate that sys.getfilesystemencoding() is used (not sys.stdin.encoding) to decode sys.argv on Linux (where sys.getfilesystemencoding() is derived from locale):

    $ LANG=C.UTF-8 PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
    '-c' '\xb5'
    $ LANG=C PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
    '-c' '\udcc2\udcb5'
    $ LANG=en_US.ISO-8859-15 PYTHONIOENCODING=latin-1 python3 -c'import sys; print(*map(ascii, sys.argv))' µ
    '-c' '\xc2\xb5'
    

    The output shows that LANG that defines locale in this case that defines sys.getfilesystemencoding() on Linux is used to decode the command-line arguments:

    $ python3
    >>> print(ascii(b'\xc2\xb5'.decode('utf-8')))
    '\xb5'
    >>> print(ascii(b'\xc2\xb5'.decode('ascii', 'surrogateescape')))
    '\udcc2\udcb5'
    >>> print(ascii(b'\xc2\xb5'.decode('iso-8859-15')))
    '\xc2\xb5'
    

提交回复
热议问题