Skip to content

gh-84037: Expand ~ in glob.glob #132757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Doc/library/glob.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The :mod:`glob` module defines the following functions:


.. function:: glob(pathname, *, root_dir=None, dir_fd=None, recursive=False, \
include_hidden=False)
include_hidden=False, expand_tilde=False)

Return a possibly empty list of path names that match *pathname*, which must be
a string containing a path specification. *pathname* can be either absolute
Expand Down Expand Up @@ -68,6 +68,8 @@ The :mod:`glob` module defines the following functions:

If *include_hidden* is true, "``**``" pattern will match hidden directories.

If *expand_tilde* is true, ``~`` will be expanded to the users home directory.

.. audit-event:: glob.glob pathname,recursive glob.glob
.. audit-event:: glob.glob/2 pathname,recursive,root_dir,dir_fd glob.glob

Expand All @@ -88,6 +90,9 @@ The :mod:`glob` module defines the following functions:
.. versionchanged:: 3.11
Added the *include_hidden* parameter.

.. versionchanged:: next
Added the *expand_tilde* parameter.


.. function:: iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False, \
include_hidden=False)
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,13 @@ getopt
(Contributed by Serhiy Storchaka in :gh:`126390`.)


glob
----

* Add *expand_tilde* option to :func:`~glob.glob`.
(Contributed by Stan Ulbrych in :gh:`132757`.)


graphlib
--------

Expand Down
14 changes: 11 additions & 3 deletions Lib/glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
__all__ = ["glob", "iglob", "escape", "translate"]

def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
include_hidden=False):
include_hidden=False, expand_tilde=False):
"""Return a list of paths matching a pathname pattern.

The pattern may contain simple shell-style wildcards a la
Expand All @@ -29,10 +29,10 @@ def glob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
zero or more directories and subdirectories.
"""
return list(iglob(pathname, root_dir=root_dir, dir_fd=dir_fd, recursive=recursive,
include_hidden=include_hidden))
include_hidden=include_hidden, expand_tilde=expand_tilde))

def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
include_hidden=False):
include_hidden=False, expand_tilde=False):
"""Return an iterator which yields the paths matching a pathname pattern.

The pattern may contain simple shell-style wildcards a la
Expand All @@ -45,6 +45,14 @@ def iglob(pathname, *, root_dir=None, dir_fd=None, recursive=False,
"""
sys.audit("glob.glob", pathname, recursive)
sys.audit("glob.glob/2", pathname, recursive, root_dir, dir_fd)

if expand_tilde:
tilde = '~' if isinstance(pathname, str) else b'~'
sep = os.path.sep if isinstance(pathname, str) else os.path.sep.encode('ascii')
home = str(os.path.expanduser('~')) if isinstance(pathname, str) else os.path.expanduser('~').encode('ascii')
if pathname == tilde or pathname.startswith(tilde + sep):
pathname = pathname.replace(tilde, home, 1)

if root_dir is not None:
root_dir = os.fspath(root_dir)
else:
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import shutil
import sys
import unittest
import unittest.mock
import warnings

from test.support import is_wasi, Py_DEBUG
Expand Down Expand Up @@ -210,6 +211,21 @@ def test_glob_bytes_directory_with_trailing_slash(self):
[os.fsencode(self.norm('aaa') + os.sep),
os.fsencode(self.norm('aab') + os.sep)])

def test_glob_tilde_expansion(self):
with unittest.mock.patch('os.path.expanduser', return_value=self.tempdir):
results = glob.glob('~', expand_tilde=True)
self.assertEqual([self.tempdir], results)

results = glob.glob(f'~{os.sep}*', expand_tilde=True)
self.assertIn(self.tempdir + f'{os.sep}a', results)

# test it is not expanded when it is not a path
tilde_file = os.path.join(self.tempdir, '~file')
create_empty_file(tilde_file)
with change_cwd(self.tempdir):
results = glob.glob('~*', expand_tilde=True)
self.assertIn('~file', results)

@skip_unless_symlink
def test_glob_symlinks(self):
eq = self.assertSequencesEqual_noorder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement ``~`` expansion in :func:`glob.glob`.
Loading