Function: eshell-parse-external-pipeline

eshell-parse-external-pipeline is a byte-compiled function defined in em-extpipe.el.gz.

Signature

(eshell-parse-external-pipeline)

Documentation

Parse a pipeline intended for execution by the external shell.

A sequence of arguments is rewritten to use the operating system shell when it contains *|, *< or *>, where the asterisk is preceded by whitespace or located at the start of input.

The command extends to the next | character which is not preceded by an unescaped asterisk following whitespace, or the end of input, except that any Eshell-specific output redirections occurring at the end are excluded. Any other < or > appearing before the end of the command are treated as though preceded by (whitespace and) an asterisk.

For example,

    foo <bar *| baz >#<buffer quux>

is equivalent to

    sh -c "foo <bar | baz" >#<buffer quux>

when shell-file-name is sh and shell-command-switch is
-c, but in

    foo >#<buffer quux> *| baz

and

    foo *| baz >#<buffer quux> --some-argument

the Eshell-specific redirect will be passed on to the operating system shell, probably leading to undesired results.

This function must appear early in eshell-parse-argument-hook to ensure that operating system shell syntax is not interpreted as though it were Eshell syntax.

Source Code

;; Defined in /usr/src/emacs/lisp/eshell/em-extpipe.el.gz
(defun eshell-parse-external-pipeline ()
  "Parse a pipeline intended for execution by the external shell.

A sequence of arguments is rewritten to use the operating system
shell when it contains `*|', `*<' or `*>', where the asterisk is
preceded by whitespace or located at the start of input.

The command extends to the next `|' character which is not
preceded by an unescaped asterisk following whitespace, or the
end of input, except that any Eshell-specific output redirections
occurring at the end are excluded.  Any other `<' or `>'
appearing before the end of the command are treated as though
preceded by (whitespace and) an asterisk.

For example,

    foo <bar *| baz >#<buffer quux>

is equivalent to

    sh -c \"foo <bar | baz\" >#<buffer quux>

when `shell-file-name' is `sh' and `shell-command-switch' is
`-c', but in

    foo >#<buffer quux> *| baz

and

    foo *| baz >#<buffer quux> --some-argument

the Eshell-specific redirect will be passed on to the operating
system shell, probably leading to undesired results.

This function must appear early in `eshell-parse-argument-hook'
to ensure that operating system shell syntax is not interpreted
as though it were Eshell syntax."
  ;; Our goal is to wrap the external command to protect it from the
  ;; other members of `eshell-parse-argument-hook'.  We must avoid
  ;; misinterpreting a quoted `*|', `*<' or `*>' as indicating an
  ;; external pipeline, hence the structure of the loop in `findbeg1'.
  (unless eshell-current-argument-plain
    (cl-flet
        ((findbeg1 (pat &optional go (bound (point-max)))
           (let* ((start (point))
                  (result
                   (catch 'found
                     (while (> bound (point))
                       (let* ((found
                               (save-excursion
                                 (re-search-forward
                                  "\\(?:#?'\\|\"\\|\\\\\\)" bound t)))
                              (next (or (and found (match-beginning 0))
                                        bound)))
                         (if (re-search-forward pat next t)
                             (throw 'found (match-beginning 1))
                           (goto-char next)
                           (while (eshell-extpipe--or-with-catch
                                   (eshell-parse-lisp-argument)
                                   (eshell-parse-backslash)
                                   (eshell-parse-double-quote)
                                   (eshell-parse-literal-quote)))
                           ;; Guard against an infinite loop if none of
                           ;; the parsers moved us forward.
                           (unless (or (> (point) next) (eobp))
                             (forward-char 1))))))))
             (goto-char (if (and result go) (match-end 0) start))
             result)))
      (unless (or eshell-current-argument eshell-current-quoted)
        (let ((beg (point)) end
              (next-marked (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)"))
              (next-unmarked
               (or (findbeg1 "\\(?:\\=\\|[^*]\\|\\S-\\*\\)\\(|\\)")
                   (point-max))))
          (when (and next-marked (> next-unmarked next-marked)
                     (or (> next-marked (point))
                         (looking-back "\\`\\|\\s-" nil)))
            ;; Skip to the final segment of the external pipeline.
            (while (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*|\\)" t))
            ;; Find output redirections.
            (while (findbeg1
                    "\\([0-9]?>+&?[0-9]?\\s-*\\S-\\)" t next-unmarked)
              ;; Is the output redirection Eshell-specific?  We have our
              ;; own logic, rather than calling `eshell-parse-argument',
              ;; to avoid specifying here all the possible cars of
              ;; parsed special references -- `get-buffer-create' etc.
              (forward-char -1)
              (let ((this-end
                     (save-match-data
                       (cond ((looking-at "#<")
                              (forward-char 1)
                              (1+ (eshell-find-delimiter ?\< ?\>)))
                             ((and (looking-at "/\\S-+")
                                   (assoc (match-string 0)
                                          eshell-virtual-targets))
                              (match-end 0))))))
                (cond ((and this-end end)
                       (goto-char this-end))
                      (this-end
                       (goto-char this-end)
                       (setq end (match-beginning 0)))
                      (t
                       (setq end nil)))))
            ;; We've moved past all Eshell-specific output redirections
            ;; we could find.  If there is only whitespace left, then
            ;; `end' is right before redirections we should exclude;
            ;; otherwise, we must include everything.
            (unless (and end (skip-syntax-forward "\s" next-unmarked)
                         (= next-unmarked (point)))
              (setq end next-unmarked))
            (let ((cmd (string-trim
                        (buffer-substring-no-properties beg end))))
              (goto-char end)
              ;; We must now drop the asterisks, unless quoted/escaped.
              (with-temp-buffer
                (insert cmd)
                (goto-char (point-min))
                (cl-loop
                 for next = (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)" t)
                 while next do (forward-char -2) (delete-char 1))
                (eshell-finish-arg
                 `(eshell-external-pipeline ,(buffer-string)))))))))))