Improve handling of Tramp internal shell scripts

* doc/misc/tramp.texi (New operations): Mention tramp-expand-script.

* lisp/net/tramp-sh.el (tramp-uudecode)
(tramp-readlink-file-truename, tramp-perl-file-truename)
(tramp-perl-file-name-all-completions)
(tramp-shell-file-name-all-completions)
(tramp-perl-file-attributes)
(tramp-perl-directory-files-and-attributes, tramp-perl-id)
(tramp-python-id, tramp-perl-encode, tramp-perl-decode)
(tramp-awk-encode, tramp-awk-decode)
(tramp-bundle-read-file-names): Indent script for better readability.
(tramp-perl-file-name-all-completions)
(tramp-shell-file-name-all-completions)
(tramp-ls-file-attributes, tramp-bundle-read-file-names):
Adapt docstring.
(tramp-shell-print-quoted-string): New defconst.
(tramp-shell-file-name-all-completions): Use "%b" format specifier.
(tramp-bundle-read-file-names):  Use "%k" format specifier.
(tramp-sh-handle-file-name-all-completions): Don't send
`tramp-bundle-read-file-names'.
(tramp-expand-script): Adapt docstring.  Apply always
`format-spec'.  Handle also "%b" and "%k" format specifiers.
This commit is contained in:
Michael Albinus 2025-11-09 13:11:49 +01:00
parent 0483178219
commit 782ca15a88
2 changed files with 138 additions and 118 deletions

View file

@ -6848,6 +6848,11 @@ they are kept. Example:
@end lisp
@end defun
@findex tramp-expand-script
Shell scripts intended for the @code{tramp-sh} backend are used as a
format string. They must observe the restrictions for format
specifiers, as documented in @code{tramp-expand-script}.
@node Traces and Profiles
@chapter How to Customize Traces

View file

@ -687,8 +687,8 @@ This list is used for copying/renaming with out-of-band methods.
See `tramp-actions-before-shell' for more info.")
(defconst tramp-uudecode
"(echo begin 600 %t; tail -n +2) | uudecode
(defconst tramp-uudecode "\
(echo begin 600 %t; tail -n +2) | uudecode
cat %t
rm -f %t"
"Shell function to implement `uudecode' to standard output.
@ -698,16 +698,16 @@ we have this shell function.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-readlink-file-truename
"if %m -h \"$1\"; then echo t; else echo nil; fi
(defconst tramp-readlink-file-truename "\
if %m -h \"$1\"; then echo t; else echo nil; fi
%r \"$1\""
"Shell script to produce output suitable for use with `file-truename'
on the remote file system.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-perl-file-truename
"%p -e '
(defconst tramp-perl-file-truename "\
%p -e '
use File::Spec;
use Cwd \"realpath\";
@ -755,8 +755,8 @@ on the remote file system.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-perl-file-name-all-completions
"%p -e '
(defconst tramp-perl-file-name-all-completions "\
%p -e '
$dir = $ARGV[0];
if ($dir ne \"/\") {
$dir =~ s#/+$##;
@ -778,20 +778,28 @@ print \")\\n\";
' \"$1\" %n"
"Perl script to produce output suitable for use with
`file-name-all-completions' on the remote file system. It returns the
same format as `tramp-bundle-read-file-names'. Format specifiers are
replaced by `tramp-expand-script', percent characters need to be
doubled.")
same format as `tramp-bundle-read-file-names'.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-shell-file-name-all-completions
"cd \"$1\" 2>&1; %l -a %n | while IFS= read file; do
quoted=`echo \"$1/$file\" | sed -e \"s#//#/#g\"`
printf \"%%s\\n\" \"$quoted\"
done | tramp_bundle_read_file_names"
(defconst tramp-shell-print-quoted-string "\
quoted=`echo \"$1\" | sed -e \"s/\\\"/\\\\\\\\\\\\\\\\\\\"/g\"`
printf \"\\\"%%s\\\"\" \"$quoted\""
"Shell script to print a lispy string.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-shell-file-name-all-completions "\
cd \"$1\" 2>&1; %l -a %n | while IFS= read file; do
quoted=`echo \"$1/$file\" | sed -e \"s#//#/#g\"`
printf \"%%s\\n\" \"$quoted\"
done | %b"
"Shell script to produce output suitable for use with
`file-name-all-completions' on the remote file system. It returns the
same format as `tramp-bundle-read-file-names'. Format specifiers are
replaced by `tramp-expand-script', percent characters need to be
doubled.")
same format as `tramp-bundle-read-file-names', which must be declared on
the remote host as well.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
;; Perl script to implement `file-attributes' in a Lisp `read'able
;; output. If you are hacking on this, note that you get *no* output
@ -799,8 +807,8 @@ doubled.")
;; end.
;; The device number is returned as "-1", because there will be a virtual
;; device number set in `tramp-sh-handle-file-attributes'.
(defconst tramp-perl-file-attributes
"%p -e '
(defconst tramp-perl-file-attributes "\
%p -e '
@stat = lstat($ARGV[0]);
if (!@stat) {
print \"nil\\n\";
@ -882,12 +890,11 @@ characters need to be doubled.")
(defconst tramp-ls-file-attributes
"%s -ild %s \"$1\" || return\n%s -lnd%s %s \"$1\""
"Shell function to produce output suitable for use with `file-attributes'
on the remote file system.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
on the remote file system. The \"%s\" format specifiers are replaced
when called in `tramp-do-file-attributes-with-ls'.")
(defconst tramp-perl-directory-files-and-attributes
"%p -e '
(defconst tramp-perl-directory-files-and-attributes "\
%p -e '
chdir($ARGV[0]) or printf(\"\\\"Cannot change to $ARGV[0]: $''!''\\\"\\n\"), exit();
opendir(DIR,\".\") or printf(\"\\\"Cannot open directory $ARGV[0]: $''!''\\\"\\n\"), exit();
@list = readdir(DIR);
@ -991,8 +998,8 @@ characters need to be doubled.")
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-perl-id
"%p -e '
(defconst tramp-perl-id "\
%p -e '
use strict;
use warnings;
use POSIX qw(getgroups);
@ -1007,8 +1014,8 @@ printf \"uid=%%d(%%s) gid=%%d(%%s) groups=%%s\\n\",
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-python-id
"%y -c '
(defconst tramp-python-id "\
%y -c '
import os, pwd, grp;
def idform(id):
@ -1043,9 +1050,9 @@ on the remote host.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-perl-encode
"%p -e '
# This script contributed by Juanma Barranquero <lektu@terra.es>.
;; This script contributed by Juanma Barranquero <lektu@terra.es>.
(defconst tramp-perl-encode "\
%p -e '
use strict;
my %%trans = do {
@ -1081,9 +1088,9 @@ while (read STDIN, $data, 54) {
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-perl-decode
"%p -e '
# This script contributed by Juanma Barranquero <lektu@terra.es>.
;; This script contributed by Juanma Barranquero <lektu@terra.es>.
(defconst tramp-perl-decode "\
%p -e '
use strict;
my %%trans = do {
@ -1141,8 +1148,8 @@ characters need to be doubled.")
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-awk-encode
"%a '\\
(defconst tramp-awk-encode "\
%a '\\
BEGIN {
b64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"
b16 = \"0123456789abcdef\"
@ -1192,8 +1199,8 @@ characters need to be doubled.")
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-awk-decode
"%a '\\
(defconst tramp-awk-decode "\
%a '\\
BEGIN {
b64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"
}
@ -1219,24 +1226,23 @@ BEGIN {
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
(defconst tramp-bundle-read-file-names
"echo \"(\"
(defconst tramp-bundle-read-file-names "\
printf \"(\\n\"
while IFS= read file; do
quoted=`echo \"$file\" | sed -e \"s/\\\"/\\\\\\\\\\\\\\\\\\\"/g\"`
printf \"(%%s\" \"\\\"$quoted\\\"\"
printf \"(\"; %k \"$file\"
if %q \"$file\"; then printf \" %%s\" t; else printf \" %%s\" nil; fi
if %m -r \"$file\"; then printf \" %%s\" t; else printf \" %%s\" nil; fi
if %m -d \"$file\"; then printf \" %%s\" t; else printf \" %%s\" nil; fi
if %m -x \"$file\"; then printf \" %%s)\\n\" t; else printf \" %%s)\\n\" nil; fi
done
echo \")\""
printf \")\\n\""
"Shell script to check file attributes of a bundle of files.
For every file, it returns a list with the absolute file name, and the
tests for file existence, file readability, file directory, and file
executable. Input shall be read via here-document, otherwise the
command could exceed maximum length of command line. Format specifiers
\"%s\" are replaced before the script is used, percent characters need
to be doubled.")
command could exceed maximum length of command line.
Format specifiers are replaced by `tramp-expand-script', percent
characters need to be doubled.")
;; New handlers should be added here.
;;;###tramp-autoload
@ -2001,9 +2007,6 @@ ID-FORMAT valid values are `string' and `integer'."
(tramp-maybe-send-script
v tramp-perl-file-name-all-completions
"tramp_perl_file_name_all_completions")
;; Used in `tramp-shell-file-name-all-completions'.
(tramp-maybe-send-script
v tramp-bundle-read-file-names "tramp_bundle_read_file_names")
(tramp-maybe-send-script
v tramp-shell-file-name-all-completions
"tramp_shell_file_name_all_completions"))
@ -4052,79 +4055,91 @@ Fall back to normal file name handler if no Tramp handler exists."
\"%a\", \"%h\", \"%l\", \"%m\", \"%o\", \"%p\", \"%q\", \"%r\", \"%s\"
and \"%y\" format specifiers are replaced by the respective `awk',
`hexdump', `ls', `test', od', `perl', `test -e', `readlink', `stat' and
`python' commands. \"%n\" is replaced by \"2>/dev/null\", and \"%t\" is
replaced by a temporary file name. If VEC is nil, the respective local
commands are used.
`python' commands.
\"%b\" is replaced by a call of \"tramp_bundle_read_file-names\", \"%k\"
is replaced by a call of \"tramp_shell_print_quoted_string\", \"%n\" is
replaced by \"2>/dev/null\", and \"%t\" is replaced by a temporary file
name.
\"%%\" is replaced by \"%\". If one of the format specifiers cannot be
expanded, this function returns nil. If there are only other format
specifiers, SCRIPT is returned unchanged."
(if (not (string-match-p (rx "%" (any "ahlmnopqrsty%")) script))
script
(catch 'wont-work
(let ((awk (when (string-match-p (rx (| bol (not "%")) "%a") script)
(or
(if vec (tramp-get-remote-awk vec) (executable-find "awk"))
(throw 'wont-work nil))))
(hdmp (when (string-match-p (rx (| bol (not "%")) "%h") script)
(or
(if vec (tramp-get-remote-hexdump vec)
(executable-find "hexdump"))
(throw 'wont-work nil))))
(dev (when (string-match-p (rx (| bol (not "%")) "%n") script)
(or
(if vec (concat "2>" (tramp-get-remote-null-device vec))
(if (eq system-type 'windows-nt) ""
(concat "2>" null-device)))
(throw 'wont-work nil))))
(ls (when (string-match-p (rx (| bol (not "%")) "%l") script)
(format "%s %s"
(or (tramp-get-ls-command vec)
(throw 'wont-work nil))
(tramp-sh--quoting-style-options vec))))
(test (when (string-match-p (rx (| bol (not "%")) "%m") script)
(or (tramp-get-test-command vec)
(throw 'wont-work nil))))
(test-e (when (string-match-p (rx (| bol (not "%")) "%q") script)
(or (tramp-get-file-exists-command vec)
(throw 'wont-work nil))))
(od (when (string-match-p (rx (| bol (not "%")) "%o") script)
(or (if vec (tramp-get-remote-od vec) (executable-find "od"))
specifiers, SCRIPT is returned unchanged.
If VEC is nil, the respective local commands are used."
(catch 'wont-work
(let ((awk (when (string-match-p (rx (| bol (not "%")) "%a") script)
(or
(if vec (tramp-get-remote-awk vec) (executable-find "awk"))
(throw 'wont-work nil))))
(bundle (when (string-match-p (rx (| bol (not "%")) "%b") script)
(tramp-maybe-send-script
vec tramp-bundle-read-file-names
"tramp_bundle_read_file-names")
"tramp_bundle_read_file-names"))
(hdmp (when (string-match-p (rx (| bol (not "%")) "%h") script)
(or
(if vec (tramp-get-remote-hexdump vec)
(executable-find "hexdump"))
(throw 'wont-work nil))))
(dev (when (string-match-p (rx (| bol (not "%")) "%n") script)
(or
(if vec (concat "2>" (tramp-get-remote-null-device vec))
(if (eq system-type 'windows-nt) ""
(concat "2>" null-device)))
(throw 'wont-work nil))))
(lispy (when (string-match-p (rx (| bol (not "%")) "%k") script)
(tramp-maybe-send-script
vec tramp-shell-print-quoted-string
"tramp_shell_print_quoted_string")
"tramp_shell_print_quoted_string"))
(ls (when (string-match-p (rx (| bol (not "%")) "%l") script)
(format "%s %s"
(or (tramp-get-ls-command vec)
(throw 'wont-work nil))
(tramp-sh--quoting-style-options vec))))
(test (when (string-match-p (rx (| bol (not "%")) "%m") script)
(or (tramp-get-test-command vec)
(throw 'wont-work nil))))
(perl (when (string-match-p (rx (| bol (not "%")) "%p") script)
(test-e (when (string-match-p (rx (| bol (not "%")) "%q") script)
(or (tramp-get-file-exists-command vec)
(throw 'wont-work nil))))
(od (when (string-match-p (rx (| bol (not "%")) "%o") script)
(or (if vec (tramp-get-remote-od vec) (executable-find "od"))
(throw 'wont-work nil))))
(perl (when (string-match-p (rx (| bol (not "%")) "%p") script)
(or
(if vec
(tramp-get-remote-perl vec) (executable-find "perl"))
(throw 'wont-work nil))))
(python (when (string-match-p (rx (| bol (not "%")) "%y") script)
(or
(if vec
(tramp-get-remote-perl vec) (executable-find "perl"))
(tramp-get-remote-python vec)
(executable-find "python"))
(throw 'wont-work nil))))
(python (when (string-match-p (rx (| bol (not "%")) "%y") script)
(or
(if vec
(tramp-get-remote-python vec)
(executable-find "python"))
(throw 'wont-work nil))))
(readlink (when (string-match-p (rx (| bol (not "%")) "%r") script)
(format "%s %s"
(or
(if vec
(tramp-get-remote-readlink vec)
(executable-find "readlink"))
(throw 'wont-work nil))
"--canonicalize-missing")))
(stat (when (string-match-p (rx (| bol (not "%")) "%s") script)
(or
(if vec
(tramp-get-remote-stat vec) (executable-find "stat"))
(throw 'wont-work nil))))
(tmp (when (string-match-p (rx (| bol (not "%")) "%t") script)
(or
(if vec
(tramp-file-local-name (tramp-make-tramp-temp-name vec))
(tramp-compat-make-temp-name))
(throw 'wont-work nil)))))
(format-spec
script
(format-spec-make
?a awk ?h hdmp ?l ls ?m test ?n dev ?o od ?p perl
?q test-e ?r readlink ?s stat ?t tmp ?y python))))))
(readlink (when (string-match-p (rx (| bol (not "%")) "%r") script)
(format "%s %s"
(or
(if vec
(tramp-get-remote-readlink vec)
(executable-find "readlink"))
(throw 'wont-work nil))
"--canonicalize-missing")))
(stat (when (string-match-p (rx (| bol (not "%")) "%s") script)
(or
(if vec
(tramp-get-remote-stat vec) (executable-find "stat"))
(throw 'wont-work nil))))
(tmp (when (string-match-p (rx (| bol (not "%")) "%t") script)
(or
(if vec
(tramp-file-local-name (tramp-make-tramp-temp-name vec))
(tramp-compat-make-temp-name))
(throw 'wont-work nil)))))
(format-spec
script
(format-spec-make
?a awk ?b bundle ?h hdmp ?k lispy ?l ls ?m test ?n dev ?o od
?p perl ?q test-e ?r readlink ?s stat ?t tmp ?y python)))))
(defun tramp-maybe-send-script (vec script name)
"Define in remote shell function NAME implemented as SCRIPT.