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