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