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'.
  (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))))))))))