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