diff --git a/bash_completion b/bash_completion
index 1b1c8bef742..66d5e4fa31c 100644
--- a/bash_completion
+++ b/bash_completion
@@ -1142,6 +1142,15 @@ _comp_compgen_filedir()
-f ${_plusdirs+"${_plusdirs[@]}"}
fi
+ if ((${#toks[@]} != 0)); then
+ # Remove . and .. (as well as */. and */..) from suggestions, unless
+ # .. or */.. was typed explicitly by the user (for users who use
+ # tab-completion to append a slash after '..')
+ if [[ $cur != ?(*/).. ]]; then
+ _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"'
+ fi
+ fi
+
if ((${#toks[@]} != 0)); then
# 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir
# unit test
@@ -3042,6 +3051,13 @@ _comp_compgen_filedir_xspec()
((${#toks[@]})) || return 1
+ # Remove . and .. (as well as */. and */..) from suggestions, unless .. or
+ # */.. was typed explicitly by the user (for users who use tab-completion
+ # to append a slash after '..')
+ if [[ $cur != ?(*/).. ]]; then
+ _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' || return 1
+ fi
+
compopt -o filenames
_comp_compgen -RU toks -- -W '"${toks[@]}"'
}
diff --git a/test/fixtures/_filedir/.dotfile1 b/test/fixtures/_filedir/.dotfile1
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/fixtures/_filedir/.dotfile2 b/test/fixtures/_filedir/.dotfile2
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 b/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/fixtures/_filedir/dotdot/..data b/test/fixtures/_filedir/dotdot/..data
new file mode 120000
index 00000000000..80557a61c9c
--- /dev/null
+++ b/test/fixtures/_filedir/dotdot/..data
@@ -0,0 +1 @@
+..2016_02_01_15_04_05.123456
\ No newline at end of file
diff --git a/test/fixtures/_filedir/dotdot/..folder/.nothing_here b/test/fixtures/_filedir/dotdot/..folder/.nothing_here
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/test/t/unit/test_unit_compgen_filedir.py b/test/t/unit/test_unit_compgen_filedir.py
index 1e3844faa48..aa18868c76d 100644
--- a/test/t/unit/test_unit_compgen_filedir.py
+++ b/test/t/unit/test_unit_compgen_filedir.py
@@ -6,7 +6,7 @@
import pytest
-from conftest import assert_bash_exec, assert_complete
+from conftest import assert_bash_exec, assert_complete, bash_env_saved
@pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=")
@@ -59,7 +59,9 @@ def utf8_ctype(self, bash):
return lc_ctype
def test_1(self, bash):
- assert_bash_exec(bash, "_comp_compgen_filedir >/dev/null")
+ with bash_env_saved(bash) as bash_env:
+ bash_env.write_variable("cur", "")
+ assert_bash_exec(bash, 'cur="" _comp_compgen_filedir >/dev/null')
@pytest.mark.parametrize("funcname", "f f2".split())
def test_2(self, bash, functions, funcname):
@@ -233,3 +235,47 @@ def test_26(self, bash, functions, funcname):
def test_27(self, bash, functions, funcname, utf8_ctype):
completion = assert_complete(bash, "%s aé/" % funcname, cwd="_filedir")
assert completion == "g"
+
+ @pytest.mark.parametrize("funcname", "f f2".split())
+ def test_28_dot_1(self, bash, functions, funcname):
+ """Exclude . and .. when the completion is attempted for '.[TAB]'"""
+ completion = assert_complete(bash, r"%s ." % funcname, cwd="_filedir")
+ assert completion == [".dotfile1", ".dotfile2"]
+
+ @pytest.mark.parametrize("funcname", "f f2".split())
+ def test_28_dot_2(self, bash, functions, funcname):
+ """Exclude . and .. when the completion is attempted for 'dir/.[TAB]'"""
+ completion = assert_complete(bash, r"%s _filedir/." % funcname)
+ assert completion == [".dotfile1", ".dotfile2"]
+
+ @pytest.mark.parametrize("funcname", "f f2".split())
+ def test_28_dot_3(self, bash, functions, funcname):
+ """Include . when the completion is attempted for '..[TAB]'"""
+ completion = assert_complete(bash, r"%s .." % funcname, cwd="_filedir")
+ assert completion == "/"
+
+ @pytest.mark.parametrize("funcname", "f f2".split())
+ def test_28_dot_4(self, bash, functions, funcname):
+ """Include . when the completion is attempted for '..[TAB]'"""
+ completion = assert_complete(bash, r"%s _filedir/.." % funcname)
+ assert completion == "/"
+
+ @pytest.mark.parametrize("funcname", "f f2".split())
+ def test_29_dotdot(self, bash, functions, funcname):
+ """Complete files starting with "..".
+
+ These types of files are used by the go kubernetes atomic writer [0],
+ and presumably other types of systems, and we want to make sure they
+ will be completed correctly.
+
+ [0] https://pkg.go.dev/k8s.io/kubernetes/pkg/volume/util#AtomicWriter.Write
+ """
+ completion = assert_complete(
+ bash, r"%s .." % funcname, cwd="_filedir/dotdot/"
+ )
+ assert completion == [
+ "../",
+ "..2016_02_01_15_04_05.123456",
+ "..data",
+ "..folder/",
+ ]
diff --git a/test/t/unit/test_unit_expand_glob.py b/test/t/unit/test_unit_expand_glob.py
index 64d04a7d6fa..7c1bcde3f9c 100644
--- a/test/t/unit/test_unit_expand_glob.py
+++ b/test/t/unit/test_unit_expand_glob.py
@@ -22,7 +22,10 @@ def functions(self, bash):
def test_match_all(self, bash, functions):
output = assert_bash_exec(bash, "__tester '*'", want_output=True)
- assert output.strip() == ""
+ assert (
+ output.strip()
+ == ""
+ )
def test_match_pattern(self, bash, functions):
output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)