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