From 0695e8a1f5dd960ffc72ffd3022c324ba718b27f Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 5 Apr 2025 16:12:00 +0900 Subject: [PATCH 1/3] fix(rsync,ssh,sshfs): do not generate regular files *'\' as dirs In sed, /[^\/]/ does not mean "a character that is not a slash". It means "a character that is not a backslash or a slash". In the bracket expressions [...], slashes do not need to be escaped by a backslash, and also, a backslash is treated literally. We should instead use /[^/]/. --- completions/ssh | 8 ++++---- test/fixtures/sshfs/local_path-dir/dummy.txt | 0 "test/fixtures/sshfs/local_path-file\\" | 0 test/t/test_sshfs.py | 12 ++++++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/sshfs/local_path-dir/dummy.txt create mode 100644 "test/fixtures/sshfs/local_path-file\\" diff --git a/completions/ssh b/completions/ssh index db30ba340f7..7a8a98e0ce0 100644 --- a/completions/ssh +++ b/completions/ssh @@ -512,7 +512,7 @@ _comp_xfunc_scp_compgen_remote_files() # shellcheck disable=SC2090 _files=$(ssh -o 'Batchmode yes' "$_userhost" \ command ls -aF1dL "$_path*" 2>/dev/null | - command sed -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' -e '/[^\/]$/d') + command sed -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' -e '/[^/]$/d') else # escape problematic characters; remove executables, aliases, pipes # and sockets; add space at end of file names @@ -520,7 +520,7 @@ _comp_xfunc_scp_compgen_remote_files() _files=$(ssh -o 'Batchmode yes' "$_userhost" \ command ls -aF1dL "$_path*" 2>/dev/null | command sed -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' -e 's/[*@|=]$//g' \ - -e 's/[^\/]$/& /g') + -e 's/[^/]$/& /g') fi _comp_compgen -R split -l -- "$_files" } @@ -550,13 +550,13 @@ _comp_xfunc_scp_compgen_local_files() _comp_compgen -RU files split -l -- "$( command ls -aF1dL "${files[@]}" 2>/dev/null | command sed -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ - -e '/[^\/]$/d' -e "s/^/${1-}/" + -e '/[^/]$/d' -e "s/^/${1-}/" )" else _comp_compgen -RU files split -l -- "$( command ls -aF1dL "${files[@]}" 2>/dev/null | command sed -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ - -e 's/[*@|=]$//g' -e 's/[^\/]$/& /g' -e "s/^/${1-}/" + -e 's/[*@|=]$//g' -e 's/[^/]$/& /g' -e "s/^/${1-}/" )" fi } diff --git a/test/fixtures/sshfs/local_path-dir/dummy.txt b/test/fixtures/sshfs/local_path-dir/dummy.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git "a/test/fixtures/sshfs/local_path-file\\" "b/test/fixtures/sshfs/local_path-file\\" new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/t/test_sshfs.py b/test/t/test_sshfs.py index cb4189bf5e1..5b502461e57 100644 --- a/test/t/test_sshfs.py +++ b/test/t/test_sshfs.py @@ -1,8 +1,20 @@ import pytest +from conftest import assert_bash_exec, assert_complete + @pytest.mark.bashcomp(ignore_env=r"^[+-]_comp_cmd_scp__path_esc=") class TestSshfs: @pytest.mark.complete("sshfs ./") def test_1(self, completion): assert completion + + @pytest.mark.complete("sshfs local_path", cwd="sshfs") + def test_local_path_suffix_1(self, completion): + assert completion == "-dir/" + + def test_remote_path_ending_with_backslash(self, bash): + assert_bash_exec(bash, "ssh() { echo 'hypothetical\\'; }") + completion = assert_complete(bash, "sshfs remote_host:hypo") + assert_bash_exec(bash, "unset -f ssh") + assert not completion From 2c54a45f5263ec108cbcb27837defc1160c1cd4a Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 17 Jun 2025 22:27:06 +0900 Subject: [PATCH 2/3] fix(sshfs): work around bash-4.3 nounset --- completions/sshfs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/completions/sshfs b/completions/sshfs index 4b3f38800dc..59d0eed0eeb 100644 --- a/completions/sshfs +++ b/completions/sshfs @@ -8,9 +8,10 @@ _comp_cmd_sshfs() _comp_expand || return if [[ $cur == *:* ]]; then - _comp_compgen -x scp remote_files -d - # unlike scp and rsync, sshfs works with 1 backslash instead of 3 - COMPREPLY=("${COMPREPLY[@]//\\\\\\/\\}") + if _comp_compgen -x scp remote_files -d; then + # unlike scp and rsync, sshfs works with 1 backslash instead of 3 + COMPREPLY=("${COMPREPLY[@]//\\\\\\/\\}") + fi return fi From 2e21621f2c065c29a00a6ad02788d70280eaf68c Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sat, 5 Apr 2025 16:55:25 +0900 Subject: [PATCH 3/3] fix(rsync,scp): remove file-type marks from "ls -F" properly The type-classifier characters for named pipes (|) and sockets (=) were not properly removed. These are escaped by a backslash before removing, so the backslashes are left. In this patch, we remove the type-classifier characters before performing the backslash escaping. --- completions/ssh | 8 +++++--- test/t/test_scp.py | 26 +++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/completions/ssh b/completions/ssh index 7a8a98e0ce0..279c44b040a 100644 --- a/completions/ssh +++ b/completions/ssh @@ -519,7 +519,8 @@ _comp_xfunc_scp_compgen_remote_files() # shellcheck disable=SC2090 _files=$(ssh -o 'Batchmode yes' "$_userhost" \ command ls -aF1dL "$_path*" 2>/dev/null | - command sed -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' -e 's/[*@|=]$//g' \ + command sed -e 's/[*@|=]$//g' \ + -e 's/'"$_comp_cmd_scp__path_esc"'/'"$_escape_replacement"'/g' \ -e 's/[^/]$/& /g') fi _comp_compgen -R split -l -- "$_files" @@ -555,8 +556,9 @@ _comp_xfunc_scp_compgen_local_files() else _comp_compgen -RU files split -l -- "$( command ls -aF1dL "${files[@]}" 2>/dev/null | - command sed -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ - -e 's/[*@|=]$//g' -e 's/[^/]$/& /g' -e "s/^/${1-}/" + command sed -e 's/[*@|=]$//g' \ + -e "s/$_comp_cmd_scp__path_esc/\\\\&/g" \ + -e 's/[^/]$/& /g' -e "s/^/${1-}/" )" fi } diff --git a/test/t/test_scp.py b/test/t/test_scp.py index 3bfbfff69f2..97f0e008de9 100644 --- a/test/t/test_scp.py +++ b/test/t/test_scp.py @@ -3,7 +3,12 @@ import pytest -from conftest import assert_bash_exec, assert_complete, bash_env_saved +from conftest import ( + assert_bash_exec, + assert_complete, + bash_env_saved, + prepare_fixture_dir, +) LIVE_HOST = os.environ.get( "BASH_COMPLETION_TEST_LIVE_SSH_HOST", default="bash_completion" @@ -152,3 +157,22 @@ def test_xfunc_remote_files(self, bash): "shared/default/foo ", "shared/default/foo.d/", ] + + @pytest.fixture + def tmpdir_mkfifo(self, request, bash): + tmpdir, _, _ = prepare_fixture_dir(request, files=[], dirs=[]) + + try: + assert_bash_exec(bash, "mkfifo '%s/local_path_1-pipe'" % tmpdir) + except Exception: + pytest.skip( + "The present system does not allow creating a named pipe." + ) + + return tmpdir + + def test_local_path_mark_1(self, bash, tmpdir_mkfifo): + completion = assert_complete( + bash, "scp local_path_1-", cwd=tmpdir_mkfifo + ) + assert completion == "pipe"