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