;; this `eshell-operator' keyword gets parsed out by
;; `eshell-split-commands'. Right now the only possibility for
;; error is an incorrect output redirection specifier.
- (when (looking-at "[&|;\n]\\s-*")
- (let ((end (match-end 0)))
+ (when (looking-at (rx (group (or "&" "|" ";" "\n" "&&" "||"))
+ (* (syntax whitespace))))
(if eshell-current-argument
(eshell-finish-arg)
- (eshell-finish-arg
- (prog1
- (list 'eshell-operator
- (cond
- ((eq (char-after end) ?\&)
- (setq end (1+ end)) "&&")
- ((eq (char-after end) ?\|)
- (setq end (1+ end)) "||")
- ((eq (char-after) ?\n) ";")
- (t
- (char-to-string (char-after)))))
- (goto-char end)))))))
+ (let ((operator (match-string 1)))
+ (when (string= operator "\n")
+ (setq operator ";"))
+ (eshell-finish-arg
+ (prog1
+ `(eshell-operator ,operator)
+ (goto-char (match-end 0))))))))
(defun eshell-prepare-splice (args)
"Prepare a list of ARGS for splicing, if any arg requested a splice.
(goto-char (point-max))
(eshell-parse-arguments (point-min) (point-max))))
args))
+ ;; Split up our commands in reverse order.
(`(,sub-chains . ,sep-terms)
- (eshell-split-commands terms "[&;]" nil t))
+ (eshell-split-commands terms "[&;]" t t))
+ ;; The last command (first in our reversed list) is implicitly
+ ;; terminated by ";".
+ (sep-terms (cons ";" sep-terms))
+ (steal-handles t)
(commands
- (mapcar
- (lambda (cmd)
- (let ((sep (pop sep-terms)))
- (setq cmd (eshell-parse-pipeline cmd))
- (unless eshell-in-pipeline-p
- (setq cmd `(eshell-trap-errors ,cmd)))
- ;; Copy I/O handles so each full statement can manipulate
- ;; them if they like. Steal the handles for the last
- ;; command in the list; we won't use the originals again
- ;; anyway.
- (setq cmd `(eshell-with-copied-handles ,cmd ,(not sep)))
- (when (equal sep "&")
- (setq cmd `(eshell-do-subjob ,cmd)))
- cmd))
- sub-chains)))
+ (nreverse
+ (mapcan
+ (lambda (cmd)
+ (let ((sep (pop sep-terms)))
+ (if (null cmd)
+ (when (equal sep "&")
+ (error "Empty command before `&'"))
+ (setq cmd (eshell-parse-pipeline cmd))
+ (unless eshell-in-pipeline-p
+ (setq cmd `(eshell-trap-errors ,cmd)))
+ ;; Copy I/O handles so each full statement can manipulate
+ ;; them if they like. Steal the handles for the last
+ ;; command (first in our reversed list); we won't use the
+ ;; originals again anyway.
+ (setq cmd `(eshell-with-copied-handles ,cmd ,steal-handles)
+ steal-handles nil)
+ (when (equal sep "&")
+ (setq cmd `(eshell-do-subjob ,cmd)))
+ (list cmd))))
+ sub-chains))))
(if toplevel
`(eshell-commands (progn
(run-hooks 'eshell-pre-command-hook)
(push (nreverse sub-terms) sub-chains)
(setq sub-terms nil))
(push term sub-terms)))
- (when sub-terms
+ (when terms
(push (nreverse sub-terms) sub-chains))
(unless reversed
(setq sub-chains (nreverse sub-chains)
\f
;; Error handling
+(ert-deftest esh-cmd-test/empty-background-command ()
+ "Test that Eshell reports an error when trying to background a nil command."
+ (with-temp-eshell
+ (eshell-match-command-output "echo hi & &"
+ "\\`Empty command before `&'\n")
+ ;; Make sure the next Eshell prompt has the original input so the
+ ;; user can fix it.
+ (should (equal (buffer-substring eshell-last-output-end (point))
+ "echo hi & &"))))
+
(ert-deftest esh-cmd-test/throw ()
"Test that calling `throw' as an Eshell command unwinds everything properly."
(with-temp-eshell