* lisp/emacs-lisp/pcase.el (pcase-defmacro): New macro.
[emacs.git] / lisp / eshell / em-unix.el
blob1e1059ae08d66febbf6e73aaaa9fbb7558d3a400
1 ;;; em-unix.el --- UNIX command aliases -*- lexical-binding:t -*-
3 ;; Copyright (C) 1999-2014 Free Software Foundation, Inc.
5 ;; Author: John Wiegley <johnw@gnu.org>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22 ;;; Commentary:
24 ;; This file contains implementations of several UNIX command in Emacs
25 ;; Lisp, for several reasons:
27 ;; 1) it makes them available on all platforms where the Lisp
28 ;; functions used are available
30 ;; 2) it makes their functionality accessible and modified by the
31 ;; Lisp programmer.
33 ;; 3) it allows Eshell to refrain from having to invoke external
34 ;; processes for common operations.
36 ;;; Code:
38 (require 'eshell)
39 (require 'esh-opt)
40 (require 'pcomplete)
42 ;;;###autoload
43 (progn
44 (defgroup eshell-unix nil
45 "This module defines many of the more common UNIX utilities as
46 aliases implemented in Lisp. These include mv, ln, cp, rm, etc. If
47 the user passes arguments which are too complex, or are unrecognized
48 by the Lisp variant, the external version will be called (if
49 available). The only reason not to use them would be because they are
50 usually much slower. But in several cases their tight integration
51 with Eshell makes them more versatile than their traditional cousins
52 \(such as being able to use `kill' to kill Eshell background processes
53 by name)."
54 :tag "UNIX commands in Lisp"
55 :group 'eshell-module))
57 (defcustom eshell-unix-load-hook nil
58 "A list of functions to run when `eshell-unix' is loaded."
59 :version "24.1" ; removed eshell-unix-initialize
60 :type 'hook
61 :group 'eshell-unix)
63 (defcustom eshell-plain-grep-behavior nil
64 "If non-nil, standalone \"grep\" commands will behave normally.
65 Standalone in this context means not redirected, and not on the
66 receiving side of a command pipeline."
67 :type 'boolean
68 :group 'eshell-unix)
70 (defcustom eshell-no-grep-available (not (eshell-search-path "grep"))
71 "If non-nil, no grep is available on the current machine."
72 :type 'boolean
73 :group 'eshell-unix)
75 (defcustom eshell-plain-diff-behavior nil
76 "If non-nil, standalone \"diff\" commands will behave normally.
77 Standalone in this context means not redirected, and not on the
78 receiving side of a command pipeline."
79 :type 'boolean
80 :group 'eshell-unix)
82 (defcustom eshell-plain-locate-behavior (featurep 'xemacs)
83 "If non-nil, standalone \"locate\" commands will behave normally.
84 Standalone in this context means not redirected, and not on the
85 receiving side of a command pipeline."
86 :type 'boolean
87 :group 'eshell-unix)
89 (defcustom eshell-rm-removes-directories nil
90 "If non-nil, `rm' will remove directory entries.
91 Otherwise, `rmdir' is required."
92 :type 'boolean
93 :group 'eshell-unix)
95 (defcustom eshell-rm-interactive-query (= (user-uid) 0)
96 "If non-nil, `rm' will query before removing anything."
97 :type 'boolean
98 :group 'eshell-unix)
100 (defcustom eshell-mv-interactive-query (= (user-uid) 0)
101 "If non-nil, `mv' will query before overwriting anything."
102 :type 'boolean
103 :group 'eshell-unix)
105 (defcustom eshell-mv-overwrite-files t
106 "If non-nil, `mv' will overwrite files without warning."
107 :type 'boolean
108 :group 'eshell-unix)
110 (defcustom eshell-cp-interactive-query (= (user-uid) 0)
111 "If non-nil, `cp' will query before overwriting anything."
112 :type 'boolean
113 :group 'eshell-unix)
115 (defcustom eshell-cp-overwrite-files t
116 "If non-nil, `cp' will overwrite files without warning."
117 :type 'boolean
118 :group 'eshell-unix)
120 (defcustom eshell-ln-interactive-query (= (user-uid) 0)
121 "If non-nil, `ln' will query before overwriting anything."
122 :type 'boolean
123 :group 'eshell-unix)
125 (defcustom eshell-ln-overwrite-files nil
126 "If non-nil, `ln' will overwrite files without warning."
127 :type 'boolean
128 :group 'eshell-unix)
130 (defcustom eshell-default-target-is-dot nil
131 "If non-nil, the default destination for cp, mv or ln is `.'."
132 :type 'boolean
133 :group 'eshell-unix)
135 (defcustom eshell-du-prefer-over-ange nil
136 "Use Eshell's du in ange-ftp remote directories.
137 Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
138 :type 'boolean
139 :group 'eshell-unix)
141 ;;; Functions:
143 (defun eshell-unix-initialize ()
144 "Initialize the UNIX support/emulation code."
145 (when (eshell-using-module 'eshell-cmpl)
146 (add-hook 'pcomplete-try-first-hook
147 'eshell-complete-host-reference nil t))
148 (make-local-variable 'eshell-complex-commands)
149 (setq eshell-complex-commands
150 (append '("grep" "egrep" "fgrep" "agrep" "glimpse" "locate"
151 "cat" "time" "cp" "mv" "make" "du" "diff")
152 eshell-complex-commands)))
154 (defalias 'eshell/date 'current-time-string)
155 (defalias 'eshell/basename 'file-name-nondirectory)
156 (defalias 'eshell/dirname 'file-name-directory)
158 (defvar em-interactive)
159 (defvar em-preview)
160 (defvar em-recursive)
161 (defvar em-verbose)
163 (defun eshell/man (&rest args)
164 "Invoke man, flattening the arguments appropriately."
165 (funcall 'man (apply 'eshell-flatten-and-stringify args)))
167 (put 'eshell/man 'eshell-no-numeric-conversions t)
169 (defun eshell/info (&rest args)
170 "Run the info command in-frame with the same behavior as command-line `info', ie:
171 'info' => goes to top info window
172 'info arg1' => IF arg1 is a file, then visits arg1
173 'info arg1' => OTHERWISE goes to top info window and then menu item arg1
174 'info arg1 arg2' => does action for arg1 (either visit-file or menu-item) and then menu item arg2
175 etc."
176 (eval-and-compile (require 'info))
177 (let ((file (cond
178 ((not (stringp (car args)))
179 nil)
180 ((file-exists-p (expand-file-name (car args)))
181 (expand-file-name (car args)))
182 ((file-exists-p (concat (expand-file-name (car args)) ".info"))
183 (concat (expand-file-name (car args)) ".info")))))
185 ;; If the first arg is a file, then go to that file's Top node
186 ;; Otherwise, go to the global directory
187 (if file
188 (progn
189 (setq args (cdr args))
190 (Info-find-node file "Top"))
191 (Info-directory))
193 ;; Treat all remaining args as menu references
194 (while args
195 (Info-menu (car args))
196 (setq args (cdr args)))))
198 (defun eshell-remove-entries (files &optional toplevel)
199 "Remove all of the given FILES, perhaps interactively."
200 (while files
201 (if (string-match "\\`\\.\\.?\\'"
202 (file-name-nondirectory (car files)))
203 (if toplevel
204 (eshell-error "rm: cannot remove `.' or `..'\n"))
205 (if (and (file-directory-p (car files))
206 (not (file-symlink-p (car files))))
207 (progn
208 (if em-verbose
209 (eshell-printn (format "rm: removing directory `%s'"
210 (car files))))
211 (unless
212 (or em-preview
213 (and em-interactive
214 (not (y-or-n-p
215 (format "rm: remove directory `%s'? "
216 (car files))))))
217 (eshell-funcalln 'delete-directory (car files) t t)))
218 (if em-verbose
219 (eshell-printn (format "rm: removing file `%s'"
220 (car files))))
221 (unless (or em-preview
222 (and em-interactive
223 (not (y-or-n-p
224 (format "rm: remove `%s'? "
225 (car files))))))
226 (eshell-funcalln 'delete-file (car files) t))))
227 (setq files (cdr files))))
229 (defun eshell/rm (&rest args)
230 "Implementation of rm in Lisp.
231 This is implemented to call either `delete-file', `kill-buffer',
232 `kill-process', or `unintern', depending on the nature of the
233 argument."
234 (setq args (eshell-flatten-list args))
235 (eshell-eval-using-options
236 "rm" args
237 '((?h "help" nil nil "show this usage screen")
238 (?f "force" nil force-removal "force removal")
239 (?i "interactive" nil em-interactive "prompt before any removal")
240 (?n "preview" nil em-preview "don't change anything on disk")
241 (?r "recursive" nil em-recursive
242 "remove the contents of directories recursively")
243 (?R nil nil em-recursive "(same)")
244 (?v "verbose" nil em-verbose "explain what is being done")
245 :preserve-args
246 :external "rm"
247 :show-usage
248 :usage "[OPTION]... FILE...
249 Remove (unlink) the FILE(s).")
250 (unless em-interactive
251 (setq em-interactive eshell-rm-interactive-query))
252 (if (and force-removal em-interactive)
253 (setq em-interactive nil))
254 (while args
255 (let ((entry (if (stringp (car args))
256 (directory-file-name (car args))
257 (if (numberp (car args))
258 (number-to-string (car args))
259 (car args)))))
260 (cond
261 ((bufferp entry)
262 (if em-verbose
263 (eshell-printn (format "rm: removing buffer `%s'" entry)))
264 (unless (or em-preview
265 (and em-interactive
266 (not (y-or-n-p (format "rm: delete buffer `%s'? "
267 entry)))))
268 (eshell-funcalln 'kill-buffer entry)))
269 ((eshell-processp entry)
270 (if em-verbose
271 (eshell-printn (format "rm: killing process `%s'" entry)))
272 (unless (or em-preview
273 (and em-interactive
274 (not (y-or-n-p (format "rm: kill process `%s'? "
275 entry)))))
276 (eshell-funcalln 'kill-process entry)))
277 ((symbolp entry)
278 (if em-verbose
279 (eshell-printn (format "rm: uninterning symbol `%s'" entry)))
280 (unless
281 (or em-preview
282 (and em-interactive
283 (not (y-or-n-p (format "rm: unintern symbol `%s'? "
284 entry)))))
285 (eshell-funcalln 'unintern entry)))
286 ((stringp entry)
287 ;; -f should silently ignore missing files (bug#15373).
288 (unless (and force-removal
289 (not (file-exists-p entry)))
290 (if (and (file-directory-p entry)
291 (not (file-symlink-p entry)))
292 (if (or em-recursive
293 eshell-rm-removes-directories)
294 (if (or em-preview
295 (not em-interactive)
296 (y-or-n-p
297 (format "rm: descend into directory `%s'? "
298 entry)))
299 (eshell-remove-entries (list entry) t))
300 (eshell-error (format "rm: %s: is a directory\n" entry)))
301 (eshell-remove-entries (list entry) t))))))
302 (setq args (cdr args)))
303 nil))
305 (put 'eshell/rm 'eshell-no-numeric-conversions t)
307 (defun eshell/mkdir (&rest args)
308 "Implementation of mkdir in Lisp."
309 (eshell-eval-using-options
310 "mkdir" args
311 '((?h "help" nil nil "show this usage screen")
312 (?p "parents" nil em-parents "make parent directories as needed")
313 :external "mkdir"
314 :show-usage
315 :usage "[OPTION] DIRECTORY...
316 Create the DIRECTORY(ies), if they do not already exist.")
317 (while args
318 (eshell-funcalln 'make-directory (car args) em-parents)
319 (setq args (cdr args)))
320 nil))
322 (put 'eshell/mkdir 'eshell-no-numeric-conversions t)
324 (defun eshell/rmdir (&rest args)
325 "Implementation of rmdir in Lisp."
326 (eshell-eval-using-options
327 "rmdir" args
328 '((?h "help" nil nil "show this usage screen")
329 :external "rmdir"
330 :show-usage
331 :usage "[OPTION] DIRECTORY...
332 Remove the DIRECTORY(ies), if they are empty.")
333 (while args
334 (eshell-funcalln 'delete-directory (car args))
335 (setq args (cdr args)))
336 nil))
338 (put 'eshell/rmdir 'eshell-no-numeric-conversions t)
340 (defvar no-dereference)
342 (defvar eshell-warn-dot-directories t)
344 (defun eshell-shuffle-files (command action files target func deep &rest args)
345 "Shuffle around some filesystem entries, using FUNC to do the work."
346 (let ((attr-target (eshell-file-attributes target))
347 (is-dir (or (file-directory-p target)
348 (and em-preview (not eshell-warn-dot-directories))))
349 attr)
350 (if (and (not em-preview) (not is-dir)
351 (> (length files) 1))
352 (error "%s: when %s multiple files, last argument must be a directory"
353 command action))
354 (while files
355 (setcar files (directory-file-name (car files)))
356 (cond
357 ((string-match "\\`\\.\\.?\\'"
358 (file-name-nondirectory (car files)))
359 (if eshell-warn-dot-directories
360 (eshell-error (format "%s: %s: omitting directory\n"
361 command (car files)))))
362 ((and attr-target
363 (or (not (eshell-under-windows-p))
364 (eq system-type 'ms-dos))
365 (setq attr (eshell-file-attributes (car files)))
366 (nth 10 attr-target) (nth 10 attr)
367 ;; Use equal, not -, since the inode and the device could
368 ;; cons cells.
369 (equal (nth 10 attr-target) (nth 10 attr))
370 (nth 11 attr-target) (nth 11 attr)
371 (equal (nth 11 attr-target) (nth 11 attr)))
372 (eshell-error (format "%s: `%s' and `%s' are the same file\n"
373 command (car files) target)))
375 (let ((source (car files))
376 (target (if is-dir
377 (expand-file-name
378 (file-name-nondirectory (car files)) target)
379 target))
380 link)
381 (if (and (file-directory-p source)
382 (or (not no-dereference)
383 (not (file-symlink-p source)))
384 (not (memq func '(make-symbolic-link
385 add-name-to-file))))
386 (if (and (eq func 'copy-file)
387 (not em-recursive))
388 (eshell-error (format "%s: %s: omitting directory\n"
389 command (car files)))
390 (let (eshell-warn-dot-directories)
391 (if (and (not deep)
392 (eq func 'rename-file)
393 ;; Use equal, since the device might be a
394 ;; cons cell.
395 (equal (nth 11 (eshell-file-attributes
396 (file-name-directory
397 (directory-file-name
398 (expand-file-name source)))))
399 (nth 11 (eshell-file-attributes
400 (file-name-directory
401 (directory-file-name
402 (expand-file-name target)))))))
403 (apply 'eshell-funcalln func source target args)
404 (unless (file-directory-p target)
405 (if em-verbose
406 (eshell-printn
407 (format "%s: making directory %s"
408 command target)))
409 (unless em-preview
410 (eshell-funcalln 'make-directory target)))
411 (apply 'eshell-shuffle-files
412 command action
413 (mapcar
414 (function
415 (lambda (file)
416 (concat source "/" file)))
417 (directory-files source))
418 target func t args)
419 (when (eq func 'rename-file)
420 (if em-verbose
421 (eshell-printn
422 (format "%s: deleting directory %s"
423 command source)))
424 (unless em-preview
425 (eshell-funcalln 'delete-directory source))))))
426 (if em-verbose
427 (eshell-printn (format "%s: %s -> %s" command
428 source target)))
429 (unless em-preview
430 (if (and no-dereference
431 (setq link (file-symlink-p source)))
432 (progn
433 (apply 'eshell-funcalln 'make-symbolic-link
434 link target args)
435 (if (eq func 'rename-file)
436 (if (and (file-directory-p source)
437 (not (file-symlink-p source)))
438 (eshell-funcalln 'delete-directory source)
439 (eshell-funcalln 'delete-file source))))
440 (apply 'eshell-funcalln func source target args)))))))
441 (setq files (cdr files)))))
443 (defun eshell-shorthand-tar-command (command args)
444 "Rewrite `cp -v dir a.tar.gz' to `tar cvzf a.tar.gz dir'."
445 (let* ((archive (car (last args)))
446 (tar-args
447 (cond ((string-match "z2" archive) "If")
448 ((string-match "gz" archive) "zf")
449 ((string-match "\\(az\\|Z\\)" archive) "Zf")
450 (t "f"))))
451 (if (file-exists-p archive)
452 (setq tar-args (concat "u" tar-args))
453 (setq tar-args (concat "c" tar-args)))
454 (if em-verbose
455 (setq tar-args (concat "v" tar-args)))
456 (if (equal command "mv")
457 (setq tar-args (concat "--remove-files -" tar-args)))
458 ;; truncate the archive name from the arguments
459 (setcdr (last args 2) nil)
460 (throw 'eshell-replace-command
461 (eshell-parse-command
462 (format "tar %s %s" tar-args archive) args))))
464 (defvar ange-cache) ; XEmacs? See esh-util
466 ;; this is to avoid duplicating code...
467 (defmacro eshell-mvcpln-template (command action func query-var
468 force-var &optional preserve)
469 `(let ((len (length args)))
470 (if (or (= len 0)
471 (and (= len 1) (null eshell-default-target-is-dot)))
472 (error "%s: missing destination file or directory" ,command))
473 (if (= len 1)
474 (nconc args '(".")))
475 (setq args (eshell-stringify-list (eshell-flatten-list args)))
476 (if (and ,(not (equal command "ln"))
477 (string-match eshell-tar-regexp (car (last args)))
478 (or (> (length args) 2)
479 (and (file-directory-p (car args))
480 (or (not no-dereference)
481 (not (file-symlink-p (car args)))))))
482 (eshell-shorthand-tar-command ,command args)
483 (let ((target (car (last args)))
484 ange-cache)
485 (setcdr (last args 2) nil)
486 (eshell-shuffle-files
487 ,command ,action args target ,func nil
488 ,@(append
489 `((if (and (or em-interactive
490 ,query-var)
491 (not force))
492 1 (or force ,force-var)))
493 (if preserve
494 (list preserve)))))
495 nil)))
497 (defun eshell/mv (&rest args)
498 "Implementation of mv in Lisp."
499 (eshell-eval-using-options
500 "mv" args
501 '((?f "force" nil force
502 "remove existing destinations, never prompt")
503 (?i "interactive" nil em-interactive
504 "request confirmation if target already exists")
505 (?n "preview" nil em-preview
506 "don't change anything on disk")
507 (?v "verbose" nil em-verbose
508 "explain what is being done")
509 (nil "help" nil nil "show this usage screen")
510 :preserve-args
511 :external "mv"
512 :show-usage
513 :usage "[OPTION]... SOURCE DEST
514 or: mv [OPTION]... SOURCE... DIRECTORY
515 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
516 \[OPTION] DIRECTORY...")
517 (let ((no-dereference t))
518 (eshell-mvcpln-template "mv" "moving" 'rename-file
519 eshell-mv-interactive-query
520 eshell-mv-overwrite-files))))
522 (put 'eshell/mv 'eshell-no-numeric-conversions t)
524 (defun eshell/cp (&rest args)
525 "Implementation of cp in Lisp."
526 (eshell-eval-using-options
527 "cp" args
528 '((?a "archive" nil archive
529 "same as -dpR")
530 (?d "no-dereference" nil no-dereference
531 "preserve links")
532 (?f "force" nil force
533 "remove existing destinations, never prompt")
534 (?i "interactive" nil em-interactive
535 "request confirmation if target already exists")
536 (?n "preview" nil em-preview
537 "don't change anything on disk")
538 (?p "preserve" nil preserve
539 "preserve file attributes if possible")
540 (?r "recursive" nil em-recursive
541 "copy directories recursively")
542 (?R nil nil em-recursive
543 "as for -r")
544 (?v "verbose" nil em-verbose
545 "explain what is being done")
546 (nil "help" nil nil "show this usage screen")
547 :preserve-args
548 :external "cp"
549 :show-usage
550 :usage "[OPTION]... SOURCE DEST
551 or: cp [OPTION]... SOURCE... DIRECTORY
552 Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
553 (if archive
554 (setq preserve t no-dereference t em-recursive t))
555 (eshell-mvcpln-template "cp" "copying" 'copy-file
556 eshell-cp-interactive-query
557 eshell-cp-overwrite-files preserve)))
559 (put 'eshell/cp 'eshell-no-numeric-conversions t)
561 (defun eshell/ln (&rest args)
562 "Implementation of ln in Lisp."
563 (eshell-eval-using-options
564 "ln" args
565 '((?h "help" nil nil "show this usage screen")
566 (?s "symbolic" nil symbolic
567 "make symbolic links instead of hard links")
568 (?i "interactive" nil em-interactive
569 "request confirmation if target already exists")
570 (?f "force" nil force "remove existing destinations, never prompt")
571 (?n "preview" nil em-preview
572 "don't change anything on disk")
573 (?v "verbose" nil em-verbose "explain what is being done")
574 :preserve-args
575 :external "ln"
576 :show-usage
577 :usage "[OPTION]... TARGET [LINK_NAME]
578 or: ln [OPTION]... TARGET... DIRECTORY
579 Create a link to the specified TARGET with optional LINK_NAME. If there is
580 more than one TARGET, the last argument must be a directory; create links
581 in DIRECTORY to each TARGET. Create hard links by default, symbolic links
582 with '--symbolic'. When creating hard links, each TARGET must exist.")
583 (let ((no-dereference t))
584 (eshell-mvcpln-template "ln" "linking"
585 (if symbolic
586 'make-symbolic-link
587 'add-name-to-file)
588 eshell-ln-interactive-query
589 eshell-ln-overwrite-files))))
591 (put 'eshell/ln 'eshell-no-numeric-conversions t)
593 (defun eshell/cat (&rest args)
594 "Implementation of cat in Lisp.
595 If in a pipeline, or the file is not a regular file, directory or
596 symlink, then revert to the system's definition of cat."
597 (setq args (eshell-stringify-list (eshell-flatten-list args)))
598 (if (or eshell-in-pipeline-p
599 (catch 'special
600 (dolist (arg args)
601 (unless (or (and (stringp arg)
602 (> (length arg) 0)
603 (eq (aref arg 0) ?-))
604 (let ((attrs (eshell-file-attributes arg)))
605 (and attrs (memq (aref (nth 8 attrs) 0)
606 '(?d ?l ?-)))))
607 (throw 'special t)))))
608 (let ((ext-cat (eshell-search-path "cat")))
609 (if ext-cat
610 (throw 'eshell-replace-command
611 (eshell-parse-command (eshell-quote-argument ext-cat) args))
612 (if eshell-in-pipeline-p
613 (error "Eshell's `cat' does not work in pipelines")
614 (error "Eshell's `cat' cannot display one of the files given"))))
615 (eshell-init-print-buffer)
616 (eshell-eval-using-options
617 "cat" args
618 '((?h "help" nil nil "show this usage screen")
619 :external "cat"
620 :show-usage
621 :usage "[OPTION] FILE...
622 Concatenate FILE(s), or standard input, to standard output.")
623 (dolist (file args)
624 (if (string= file "-")
625 (throw 'eshell-external
626 (eshell-external-command "cat" args))))
627 (let ((curbuf (current-buffer)))
628 (dolist (file args)
629 (with-temp-buffer
630 (insert-file-contents file)
631 (goto-char (point-min))
632 (while (not (eobp))
633 (let ((str (buffer-substring
634 (point) (min (1+ (line-end-position))
635 (point-max)))))
636 (with-current-buffer curbuf
637 (eshell-buffered-print str)))
638 (forward-line)))))
639 (eshell-flush)
640 ;; if the file does not end in a newline, do not emit one
641 (setq eshell-ensure-newline-p nil))))
643 (put 'eshell/cat 'eshell-no-numeric-conversions t)
645 ;; special front-end functions for compilation-mode buffers
647 (defun eshell/make (&rest args)
648 "Use `compile' to do background makes."
649 (if (and eshell-current-subjob-p
650 (eshell-interactive-output-p))
651 (let ((compilation-process-setup-function
652 (list 'lambda nil
653 (list 'setq 'process-environment
654 (list 'quote (eshell-copy-environment))))))
655 (compile (concat "make " (eshell-flatten-and-stringify args))))
656 (throw 'eshell-replace-command
657 (eshell-parse-command "*make" (eshell-stringify-list
658 (eshell-flatten-list args))))))
660 (put 'eshell/make 'eshell-no-numeric-conversions t)
662 (defun eshell-occur-mode-goto-occurrence ()
663 "Go to the occurrence the current line describes."
664 (interactive)
665 (let ((pos (occur-mode-find-occurrence)))
666 (pop-to-buffer (marker-buffer pos))
667 (goto-char (marker-position pos))))
669 (defun eshell-occur-mode-mouse-goto (event)
670 "In Occur mode, go to the occurrence whose line you click on."
671 (interactive "e")
672 (let (pos)
673 (with-current-buffer (window-buffer (posn-window (event-end event)))
674 (save-excursion
675 (goto-char (posn-point (event-end event)))
676 (setq pos (occur-mode-find-occurrence))))
677 (pop-to-buffer (marker-buffer pos))
678 (goto-char (marker-position pos))))
680 (defun eshell-poor-mans-grep (args)
681 "A poor version of grep that opens every file and uses `occur'.
682 This eats up memory, since it leaves the buffers open (to speed future
683 searches), and it's very slow. But, if your system has no grep
684 available..."
685 (save-selected-window
686 (let ((default-dir default-directory))
687 (with-current-buffer (get-buffer-create "*grep*")
688 (let ((inhibit-read-only t)
689 (default-directory default-dir))
690 (erase-buffer)
691 (occur-mode)
692 (let ((files (eshell-stringify-list
693 (eshell-flatten-list (cdr args))))
694 (inhibit-redisplay t)
695 string)
696 (when (car args)
697 (if (get-buffer "*Occur*")
698 (kill-buffer (get-buffer "*Occur*")))
699 (setq string nil)
700 (while files
701 (with-current-buffer (find-file-noselect (car files))
702 (save-excursion
703 (ignore-errors
704 (occur (car args))))
705 (if (get-buffer "*Occur*")
706 (with-current-buffer (get-buffer "*Occur*")
707 (setq string (buffer-string))
708 (kill-buffer (current-buffer)))))
709 (if string (insert string))
710 (setq string nil
711 files (cdr files)))))
712 (local-set-key [mouse-2] 'eshell-occur-mode-mouse-goto)
713 (local-set-key [(control ?c) (control ?c)]
714 'eshell-occur-mode-goto-occurrence)
715 (local-set-key [(control ?m)]
716 'eshell-occur-mode-goto-occurrence)
717 (local-set-key [return] 'eshell-occur-mode-goto-occurrence)
718 (pop-to-buffer (current-buffer) t)
719 (goto-char (point-min))
720 (resize-temp-buffer-window))))))
722 (defvar compilation-scroll-output)
724 (defun eshell-grep (command args &optional maybe-use-occur)
725 "Generic service function for the various grep aliases.
726 It calls Emacs's grep utility if the command is not redirecting output,
727 and if it's not part of a command pipeline. Otherwise, it calls the
728 external command."
729 (if (and maybe-use-occur eshell-no-grep-available)
730 (eshell-poor-mans-grep args)
731 (if (or eshell-plain-grep-behavior
732 (not (and (eshell-interactive-output-p)
733 (not eshell-in-pipeline-p)
734 (not eshell-in-subcommand-p))))
735 (throw 'eshell-replace-command
736 (eshell-parse-command (concat "*" command)
737 (eshell-stringify-list
738 (eshell-flatten-list args))))
739 (let* ((args (mapconcat 'identity
740 (mapcar 'shell-quote-argument
741 (eshell-stringify-list
742 (eshell-flatten-list args)))
743 " "))
744 (cmd (progn
745 (set-text-properties 0 (length args)
746 '(invisible t) args)
747 (format "%s -n %s" command args)))
748 compilation-scroll-output)
749 (grep cmd)))))
751 (defun eshell/grep (&rest args)
752 "Use Emacs grep facility instead of calling external grep."
753 (eshell-grep "grep" args t))
755 (defun eshell/egrep (&rest args)
756 "Use Emacs grep facility instead of calling external egrep."
757 (eshell-grep "egrep" args t))
759 (defun eshell/fgrep (&rest args)
760 "Use Emacs grep facility instead of calling external fgrep."
761 (eshell-grep "fgrep" args t))
763 (defun eshell/agrep (&rest args)
764 "Use Emacs grep facility instead of calling external agrep."
765 (eshell-grep "agrep" args))
767 (defun eshell/glimpse (&rest args)
768 "Use Emacs grep facility instead of calling external glimpse."
769 (let (null-device)
770 (eshell-grep "glimpse" (append '("-z" "-y") args))))
772 ;; completions rules for some common UNIX commands
774 (defsubst eshell-complete-hostname ()
775 "Complete a command that wants a hostname for an argument."
776 (pcomplete-here (eshell-read-host-names)))
778 (defun eshell-complete-host-reference ()
779 "If there is a host reference, complete it."
780 (let ((arg (pcomplete-actual-arg))
781 index)
782 (when (setq index (string-match "@[a-z.]*\\'" arg))
783 (setq pcomplete-stub (substring arg (1+ index))
784 pcomplete-last-completion-raw t)
785 (throw 'pcomplete-completions (eshell-read-host-names)))))
787 (defalias 'pcomplete/ftp 'eshell-complete-hostname)
788 (defalias 'pcomplete/ncftp 'eshell-complete-hostname)
789 (defalias 'pcomplete/ping 'eshell-complete-hostname)
790 (defalias 'pcomplete/rlogin 'eshell-complete-hostname)
792 (defun pcomplete/telnet ()
793 (require 'pcmpl-unix)
794 (pcomplete-opt "xl(pcmpl-unix-user-names)")
795 (eshell-complete-hostname))
797 (defun pcomplete/rsh ()
798 "Complete `rsh', which, after the user and hostname, is like xargs."
799 (require 'pcmpl-unix)
800 (pcomplete-opt "l(pcmpl-unix-user-names)")
801 (eshell-complete-hostname)
802 (pcomplete-here (funcall pcomplete-command-completion-function))
803 (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
804 pcomplete-default-completion-function)))
806 (defvar block-size)
807 (defvar by-bytes)
808 (defvar dereference-links)
809 (defvar grand-total)
810 (defvar human-readable)
811 (defvar max-depth)
812 (defvar only-one-filesystem)
813 (defvar show-all)
815 (defsubst eshell-du-size-string (size)
816 (let* ((str (eshell-printable-size size human-readable block-size t))
817 (len (length str)))
818 (concat str (if (< len 8)
819 (make-string (- 8 len) ? )))))
821 (defun eshell-du-sum-directory (path depth)
822 "Summarize PATH, and its member directories."
823 (let ((entries (eshell-directory-files-and-attributes path))
824 (size 0.0))
825 (while entries
826 (unless (string-match "\\`\\.\\.?\\'" (caar entries))
827 (let* ((entry (concat path "/"
828 (caar entries)))
829 (symlink (and (stringp (cadr (car entries)))
830 (cadr (car entries)))))
831 (unless (or (and symlink (not dereference-links))
832 (and only-one-filesystem
833 (/= only-one-filesystem
834 (nth 12 (car entries)))))
835 (if symlink
836 (setq entry symlink))
837 (setq size
838 (+ size
839 (if (eq t (cadr (car entries)))
840 (eshell-du-sum-directory entry (1+ depth))
841 (let ((file-size (nth 8 (car entries))))
842 (prog1
843 file-size
844 (if show-all
845 (eshell-print
846 (concat (eshell-du-size-string file-size)
847 entry "\n")))))))))))
848 (setq entries (cdr entries)))
849 (if (or (not max-depth)
850 (= depth max-depth)
851 (= depth 0))
852 (eshell-print (concat (eshell-du-size-string size)
853 (directory-file-name path) "\n")))
854 size))
856 (defun eshell/du (&rest args)
857 "Implementation of \"du\" in Lisp, passing ARGS."
858 (setq args (if args
859 (eshell-stringify-list (eshell-flatten-list args))
860 '(".")))
861 (let ((ext-du (eshell-search-path "du")))
862 (if (and ext-du
863 (not (catch 'have-ange-path
864 (dolist (arg args)
865 (if (string-equal
866 (file-remote-p (expand-file-name arg) 'method) "ftp")
867 (throw 'have-ange-path t))))))
868 (throw 'eshell-replace-command
869 (eshell-parse-command (eshell-quote-argument ext-du) args))
870 (eshell-eval-using-options
871 "du" args
872 '((?a "all" nil show-all
873 "write counts for all files, not just directories")
874 (nil "block-size" t block-size
875 "use SIZE-byte blocks (i.e., --block-size SIZE)")
876 (?b "bytes" nil by-bytes
877 "print size in bytes")
878 (?c "total" nil grand-total
879 "produce a grand total")
880 (?d "max-depth" t max-depth
881 "display data only this many levels of data")
882 (?h "human-readable" 1024 human-readable
883 "print sizes in human readable format")
884 (?H "is" 1000 human-readable
885 "likewise, but use powers of 1000 not 1024")
886 (?k "kilobytes" 1024 block-size
887 "like --block-size 1024")
888 (?L "dereference" nil dereference-links
889 "dereference all symbolic links")
890 (?m "megabytes" 1048576 block-size
891 "like --block-size 1048576")
892 (?s "summarize" 0 max-depth
893 "display only a total for each argument")
894 (?x "one-file-system" nil only-one-filesystem
895 "skip directories on different filesystems")
896 (nil "help" nil nil
897 "show this usage screen")
898 :external "du"
899 :usage "[OPTION]... FILE...
900 Summarize disk usage of each FILE, recursively for directories.")
901 (unless by-bytes
902 (setq block-size (or block-size 1024)))
903 (if (and max-depth (stringp max-depth))
904 (setq max-depth (string-to-number max-depth)))
905 ;; filesystem support means nothing under Windows
906 (if (eshell-under-windows-p)
907 (setq only-one-filesystem nil))
908 (let ((size 0.0) ange-cache)
909 (while args
910 (if only-one-filesystem
911 (setq only-one-filesystem
912 (nth 11 (eshell-file-attributes
913 (file-name-as-directory (car args))))))
914 (setq size (+ size (eshell-du-sum-directory
915 (directory-file-name (car args)) 0)))
916 (setq args (cdr args)))
917 (if grand-total
918 (eshell-print (concat (eshell-du-size-string size)
919 "total\n"))))))))
921 (defvar eshell-time-start nil)
923 (defun eshell-show-elapsed-time ()
924 (let ((elapsed (format "%.3f secs\n" (- (float-time) eshell-time-start))))
925 (set-text-properties 0 (length elapsed) '(face bold) elapsed)
926 (eshell-interactive-print elapsed))
927 (remove-hook 'eshell-post-command-hook 'eshell-show-elapsed-time t))
929 (defun eshell/time (&rest args)
930 "Implementation of \"time\" in Lisp."
931 (let ((time-args (copy-alist args))
932 (continue t)
933 last-arg)
934 (while (and continue args)
935 (if (not (string-match "^-" (car args)))
936 (progn
937 (if last-arg
938 (setcdr last-arg nil)
939 (setq args '("")))
940 (setq continue nil))
941 (setq last-arg args
942 args (cdr args))))
943 (eshell-eval-using-options
944 "time" args
945 '((?h "help" nil nil "show this usage screen")
946 :external "time"
947 :show-usage
948 :usage "COMMAND...
949 Show wall-clock time elapsed during execution of COMMAND.")
950 (setq eshell-time-start (float-time))
951 (add-hook 'eshell-post-command-hook 'eshell-show-elapsed-time nil t)
952 ;; after setting
953 (throw 'eshell-replace-command
954 (eshell-parse-command (car time-args)
955 ;;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-08/msg00205.html
956 (eshell-stringify-list
957 (eshell-flatten-list (cdr time-args))))))))
959 (defun eshell/whoami (&rest args)
960 "Make \"whoami\" Tramp aware."
961 (or (file-remote-p default-directory 'user) (user-login-name)))
963 (defvar eshell-diff-window-config nil)
965 (defun eshell-diff-quit ()
966 "Restore the window configuration previous to diff'ing."
967 (interactive)
968 (if eshell-diff-window-config
969 (set-window-configuration eshell-diff-window-config)))
971 (defun nil-blank-string (string)
972 "Return STRING, or nil if STRING contains only non-blank characters."
973 (cond
974 ((string-match "[^[:blank:]]" string) string)
975 (nil)))
977 (autoload 'diff-no-select "diff")
979 (defun eshell/diff (&rest args)
980 "Alias \"diff\" to call Emacs `diff' function."
981 (let ((orig-args (eshell-stringify-list (eshell-flatten-list args))))
982 (if (or eshell-plain-diff-behavior
983 (not (and (eshell-interactive-output-p)
984 (not eshell-in-pipeline-p)
985 (not eshell-in-subcommand-p))))
986 (throw 'eshell-replace-command
987 (eshell-parse-command "*diff" orig-args))
988 (setq args (copy-sequence orig-args))
989 (if (< (length args) 2)
990 (throw 'eshell-replace-command
991 (eshell-parse-command "*diff" orig-args)))
992 (let ((old (car (last args 2)))
993 (new (car (last args)))
994 (config (current-window-configuration)))
995 (if (= (length args) 2)
996 (setq args nil)
997 (setcdr (last args 3) nil))
998 (with-current-buffer
999 (condition-case nil
1000 (diff-no-select
1001 old new
1002 (nil-blank-string (eshell-flatten-and-stringify args)))
1003 (error
1004 (throw 'eshell-replace-command
1005 (eshell-parse-command "*diff" orig-args))))
1006 (when (fboundp 'diff-mode)
1007 (make-local-variable 'compilation-finish-functions)
1008 (add-hook
1009 'compilation-finish-functions
1010 `(lambda (buff msg)
1011 (with-current-buffer buff
1012 (diff-mode)
1013 (set (make-local-variable 'eshell-diff-window-config)
1014 ,config)
1015 (local-set-key [?q] 'eshell-diff-quit)
1016 (if (fboundp 'turn-on-font-lock-if-enabled)
1017 (turn-on-font-lock-if-enabled))
1018 (goto-char (point-min))))))
1019 (pop-to-buffer (current-buffer))))))
1020 nil)
1022 (put 'eshell/diff 'eshell-no-numeric-conversions t)
1024 (defvar locate-history-list)
1026 (defun eshell/locate (&rest args)
1027 "Alias \"locate\" to call Emacs `locate' function."
1028 (if (or eshell-plain-locate-behavior
1029 (not (and (eshell-interactive-output-p)
1030 (not eshell-in-pipeline-p)
1031 (not eshell-in-subcommand-p)))
1032 (and (stringp (car args))
1033 (string-match "^-" (car args))))
1034 (throw 'eshell-replace-command
1035 (eshell-parse-command "*locate" (eshell-stringify-list
1036 (eshell-flatten-list args))))
1037 (save-selected-window
1038 (let ((locate-history-list (list (car args))))
1039 (locate-with-filter (car args) (cadr args))))))
1041 (put 'eshell/locate 'eshell-no-numeric-conversions t)
1043 (defun eshell/occur (&rest args)
1044 "Alias \"occur\" to call Emacs `occur' function."
1045 (let ((inhibit-read-only t))
1046 (if (> (length args) 2)
1047 (error "usage: occur: (REGEXP &optional NLINES)")
1048 (apply 'occur args))))
1050 (put 'eshell/occur 'eshell-no-numeric-conversions t)
1052 (provide 'em-unix)
1054 ;; Local Variables:
1055 ;; generated-autoload-file: "esh-groups.el"
1056 ;; End:
1058 ;;; em-unix.el ends here