]> git.ipfire.org Git - thirdparty/bash.git/blob - builtins/pushd.def
82653c479c5f27b8c46989a908dd301d233f6a13
[thirdparty/bash.git] / builtins / pushd.def
1 This file is pushd.def, from which is created pushd.c. It implements the
2 builtins "pushd", "popd", and "dirs" in Bash.
3
4 Copyright (C) 1987-2015 Free Software Foundation, Inc.
5
6 This file is part of GNU Bash, the Bourne Again SHell.
7
8 Bash is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Bash is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Bash. If not, see <http://www.gnu.org/licenses/>.
20
21 $PRODUCES pushd.c
22
23 $BUILTIN pushd
24 $FUNCTION pushd_builtin
25 $DEPENDS_ON PUSHD_AND_POPD
26 $SHORT_DOC pushd [-n] [+N | -N | dir]
27 Add directories to stack.
28
29 Adds a directory to the top of the directory stack, or rotates
30 the stack, making the new top of the stack the current working
31 directory. With no arguments, exchanges the top two directories.
32
33 Options:
34 -n Suppresses the normal change of directory when adding
35 directories to the stack, so only the stack is manipulated.
36
37 Arguments:
38 +N Rotates the stack so that the Nth directory (counting
39 from the left of the list shown by `dirs', starting with
40 zero) is at the top.
41
42 -N Rotates the stack so that the Nth directory (counting
43 from the right of the list shown by `dirs', starting with
44 zero) is at the top.
45
46 dir Adds DIR to the directory stack at the top, making it the
47 new current working directory.
48
49 The `dirs' builtin displays the directory stack.
50
51 Exit Status:
52 Returns success unless an invalid argument is supplied or the directory
53 change fails.
54 $END
55
56 $BUILTIN popd
57 $FUNCTION popd_builtin
58 $DEPENDS_ON PUSHD_AND_POPD
59 $SHORT_DOC popd [-n] [+N | -N]
60 Remove directories from stack.
61
62 Removes entries from the directory stack. With no arguments, removes
63 the top directory from the stack, and changes to the new top directory.
64
65 Options:
66 -n Suppresses the normal change of directory when removing
67 directories from the stack, so only the stack is manipulated.
68
69 Arguments:
70 +N Removes the Nth entry counting from the left of the list
71 shown by `dirs', starting with zero. For example: `popd +0'
72 removes the first directory, `popd +1' the second.
73
74 -N Removes the Nth entry counting from the right of the list
75 shown by `dirs', starting with zero. For example: `popd -0'
76 removes the last directory, `popd -1' the next to last.
77
78 The `dirs' builtin displays the directory stack.
79
80 Exit Status:
81 Returns success unless an invalid argument is supplied or the directory
82 change fails.
83 $END
84
85 $BUILTIN dirs
86 $FUNCTION dirs_builtin
87 $DEPENDS_ON PUSHD_AND_POPD
88 $SHORT_DOC dirs [-clpv] [+N] [-N]
89 Display directory stack.
90
91 Display the list of currently remembered directories. Directories
92 find their way onto the list with the `pushd' command; you can get
93 back up through the list with the `popd' command.
94
95 Options:
96 -c clear the directory stack by deleting all of the elements
97 -l do not print tilde-prefixed versions of directories relative
98 to your home directory
99 -p print the directory stack with one entry per line
100 -v print the directory stack with one entry per line prefixed
101 with its position in the stack
102
103 Arguments:
104 +N Displays the Nth entry counting from the left of the list
105 shown by dirs when invoked without options, starting with
106 zero.
107
108 -N Displays the Nth entry counting from the right of the list
109 shown by dirs when invoked without options, starting with
110 zero.
111
112 Exit Status:
113 Returns success unless an invalid option is supplied or an error occurs.
114 $END
115
116 #include <config.h>
117
118 #if defined (PUSHD_AND_POPD)
119 #include <stdio.h>
120 #if defined (HAVE_SYS_PARAM_H)
121 # include <sys/param.h>
122 #endif
123
124 #if defined (HAVE_UNISTD_H)
125 # ifdef _MINIX
126 # include <sys/types.h>
127 # endif
128 # include <unistd.h>
129 #endif
130
131 #include "../bashansi.h"
132 #include "../bashintl.h"
133
134 #include <errno.h>
135
136 #include <tilde/tilde.h>
137
138 #include "../shell.h"
139 #include "maxpath.h"
140 #include "common.h"
141 #include "builtext.h"
142
143 #ifdef LOADABLE_BUILTIN
144 # include "builtins.h"
145 #endif
146
147 #if !defined (errno)
148 extern int errno;
149 #endif /* !errno */
150
151 /* The list of remembered directories. */
152 static char **pushd_directory_list = (char **)NULL;
153
154 /* Number of existing slots in this list. */
155 static int directory_list_size;
156
157 /* Offset to the end of the list. */
158 static int directory_list_offset;
159
160 static void pushd_error __P((int, char *));
161 static void clear_directory_stack __P((void));
162 static int cd_to_string __P((char *));
163 static int change_to_temp __P((char *));
164 static void add_dirstack_element __P((char *));
165 static int get_dirstack_index __P((intmax_t, int, int *));
166
167 #define NOCD 0x01
168 #define ROTATE 0x02
169 #define LONGFORM 0x04
170 #define CLEARSTAK 0x08
171
172 int
173 pushd_builtin (list)
174 WORD_LIST *list;
175 {
176 WORD_LIST *orig_list;
177 char *temp, *current_directory, *top;
178 int j, flags, skipopt;
179 intmax_t num;
180 char direction;
181
182 orig_list = list;
183
184 CHECK_HELPOPT (list);
185 if (list && list->word && ISOPTION (list->word->word, '-'))
186 {
187 list = list->next;
188 skipopt = 1;
189 }
190 else
191 skipopt = 0;
192
193 /* If there is no argument list then switch current and
194 top of list. */
195 if (list == 0)
196 {
197 if (directory_list_offset == 0)
198 {
199 builtin_error (_("no other directory"));
200 return (EXECUTION_FAILURE);
201 }
202
203 current_directory = get_working_directory ("pushd");
204 if (current_directory == 0)
205 return (EXECUTION_FAILURE);
206
207 j = directory_list_offset - 1;
208 temp = pushd_directory_list[j];
209 pushd_directory_list[j] = current_directory;
210 j = change_to_temp (temp);
211 free (temp);
212 return j;
213 }
214
215 for (flags = 0; skipopt == 0 && list; list = list->next)
216 {
217 if (ISOPTION (list->word->word, 'n'))
218 {
219 flags |= NOCD;
220 }
221 else if (ISOPTION (list->word->word, '-'))
222 {
223 list = list->next;
224 break;
225 }
226 else if (list->word->word[0] == '-' && list->word->word[1] == '\0')
227 /* Let `pushd -' work like it used to. */
228 break;
229 else if (((direction = list->word->word[0]) == '+') || direction == '-')
230 {
231 if (legal_number (list->word->word + 1, &num) == 0)
232 {
233 sh_invalidnum (list->word->word);
234 builtin_usage ();
235 return (EX_USAGE);
236 }
237
238 if (direction == '-')
239 num = directory_list_offset - num;
240
241 if (num > directory_list_offset || num < 0)
242 {
243 pushd_error (directory_list_offset, list->word->word);
244 return (EXECUTION_FAILURE);
245 }
246 flags |= ROTATE;
247 }
248 else if (*list->word->word == '-')
249 {
250 sh_invalidopt (list->word->word);
251 builtin_usage ();
252 return (EX_USAGE);
253 }
254 else
255 break;
256 }
257
258 if (flags & ROTATE)
259 {
260 /* Rotate the stack num times. Remember, the current
261 directory acts like it is part of the stack. */
262 temp = get_working_directory ("pushd");
263
264 if (num == 0)
265 {
266 j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS;
267 free (temp);
268 return j;
269 }
270
271 do
272 {
273 top = pushd_directory_list[directory_list_offset - 1];
274
275 for (j = directory_list_offset - 2; j > -1; j--)
276 pushd_directory_list[j + 1] = pushd_directory_list[j];
277
278 pushd_directory_list[j + 1] = temp;
279
280 temp = top;
281 num--;
282 }
283 while (num);
284
285 j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS;
286 free (temp);
287 return j;
288 }
289
290 if (list == 0)
291 return (EXECUTION_SUCCESS);
292
293 /* Change to the directory in list->word->word. Save the current
294 directory on the top of the stack. */
295 current_directory = get_working_directory ("pushd");
296 if (current_directory == 0)
297 return (EXECUTION_FAILURE);
298
299 j = ((flags & NOCD) == 0) ? cd_builtin (skipopt ? orig_list : list) : EXECUTION_SUCCESS;
300 if (j == EXECUTION_SUCCESS)
301 {
302 add_dirstack_element ((flags & NOCD) ? savestring (list->word->word) : current_directory);
303 dirs_builtin ((WORD_LIST *)NULL);
304 if (flags & NOCD)
305 free (current_directory);
306 return (EXECUTION_SUCCESS);
307 }
308 else
309 {
310 free (current_directory);
311 return (EXECUTION_FAILURE);
312 }
313 }
314
315 /* Pop the directory stack, and then change to the new top of the stack.
316 If LIST is non-null it should consist of a word +N or -N, which says
317 what element to delete from the stack. The default is the top one. */
318 int
319 popd_builtin (list)
320 WORD_LIST *list;
321 {
322 register int i;
323 intmax_t which;
324 int flags;
325 char direction;
326 char *which_word;
327
328 CHECK_HELPOPT (list);
329
330 which_word = (char *)NULL;
331 for (flags = 0, which = 0, direction = '+'; list; list = list->next)
332 {
333 if (ISOPTION (list->word->word, 'n'))
334 {
335 flags |= NOCD;
336 }
337 else if (ISOPTION (list->word->word, '-'))
338 {
339 list = list->next;
340 break;
341 }
342 else if (((direction = list->word->word[0]) == '+') || direction == '-')
343 {
344 if (legal_number (list->word->word + 1, &which) == 0)
345 {
346 sh_invalidnum (list->word->word);
347 builtin_usage ();
348 return (EX_USAGE);
349 }
350 which_word = list->word->word;
351 }
352 else if (*list->word->word == '-')
353 {
354 sh_invalidopt (list->word->word);
355 builtin_usage ();
356 return (EX_USAGE);
357 }
358 else if (*list->word->word)
359 {
360 builtin_error (_("%s: invalid argument"), list->word->word);
361 builtin_usage ();
362 return (EX_USAGE);
363 }
364 else
365 break;
366 }
367
368 if (which > directory_list_offset || (directory_list_offset == 0 && which == 0))
369 {
370 pushd_error (directory_list_offset, which_word ? which_word : "");
371 return (EXECUTION_FAILURE);
372 }
373
374 /* Handle case of no specification, or top of stack specification. */
375 if ((direction == '+' && which == 0) ||
376 (direction == '-' && which == directory_list_offset))
377 {
378 i = ((flags & NOCD) == 0) ? cd_to_string (pushd_directory_list[directory_list_offset - 1])
379 : EXECUTION_SUCCESS;
380 if (i != EXECUTION_SUCCESS)
381 return (i);
382 free (pushd_directory_list[--directory_list_offset]);
383 }
384 else
385 {
386 /* Since an offset other than the top directory was specified,
387 remove that directory from the list and shift the remainder
388 of the list into place. */
389 i = (direction == '+') ? directory_list_offset - which : which;
390 free (pushd_directory_list[i]);
391 directory_list_offset--;
392
393 /* Shift the remainder of the list into place. */
394 for (; i < directory_list_offset; i++)
395 pushd_directory_list[i] = pushd_directory_list[i + 1];
396 }
397
398 dirs_builtin ((WORD_LIST *)NULL);
399 return (EXECUTION_SUCCESS);
400 }
401
402 /* Print the current list of directories on the directory stack. */
403 int
404 dirs_builtin (list)
405 WORD_LIST *list;
406 {
407 int flags, desired_index, index_flag, vflag;
408 intmax_t i;
409 char *temp, *w;
410
411 CHECK_HELPOPT (list);
412 for (flags = vflag = index_flag = 0, desired_index = -1, w = ""; list; list = list->next)
413 {
414 if (ISOPTION (list->word->word, 'l'))
415 {
416 flags |= LONGFORM;
417 }
418 else if (ISOPTION (list->word->word, 'c'))
419 {
420 flags |= CLEARSTAK;
421 }
422 else if (ISOPTION (list->word->word, 'v'))
423 {
424 vflag |= 2;
425 }
426 else if (ISOPTION (list->word->word, 'p'))
427 {
428 vflag |= 1;
429 }
430 else if (ISOPTION (list->word->word, '-'))
431 {
432 list = list->next;
433 break;
434 }
435 else if (*list->word->word == '+' || *list->word->word == '-')
436 {
437 int sign;
438 if (legal_number (w = list->word->word + 1, &i) == 0)
439 {
440 sh_invalidnum (list->word->word);
441 builtin_usage ();
442 return (EX_USAGE);
443 }
444 sign = (*list->word->word == '+') ? 1 : -1;
445 desired_index = get_dirstack_index (i, sign, &index_flag);
446 }
447 else
448 {
449 sh_invalidopt (list->word->word);
450 builtin_usage ();
451 return (EX_USAGE);
452 }
453 }
454
455 if (flags & CLEARSTAK)
456 {
457 clear_directory_stack ();
458 return (EXECUTION_SUCCESS);
459 }
460
461 if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
462 {
463 pushd_error (directory_list_offset, w);
464 return (EXECUTION_FAILURE);
465 }
466
467 #define DIRSTACK_FORMAT(temp) \
468 (flags & LONGFORM) ? temp : polite_directory_format (temp)
469
470 /* The first directory printed is always the current working directory. */
471 if (index_flag == 0 || (index_flag == 1 && desired_index == 0))
472 {
473 temp = get_working_directory ("dirs");
474 if (temp == 0)
475 temp = savestring (_("<no current directory>"));
476 if (vflag & 2)
477 printf ("%2d %s", 0, DIRSTACK_FORMAT (temp));
478 else
479 printf ("%s", DIRSTACK_FORMAT (temp));
480 free (temp);
481 if (index_flag)
482 {
483 putchar ('\n');
484 return (sh_chkwrite (EXECUTION_SUCCESS));
485 }
486 }
487
488 #define DIRSTACK_ENTRY(i) \
489 (flags & LONGFORM) ? pushd_directory_list[i] \
490 : polite_directory_format (pushd_directory_list[i])
491
492 /* Now print the requested directory stack entries. */
493 if (index_flag)
494 {
495 if (vflag & 2)
496 printf ("%2d %s", directory_list_offset - desired_index,
497 DIRSTACK_ENTRY (desired_index));
498 else
499 printf ("%s", DIRSTACK_ENTRY (desired_index));
500 }
501 else
502 for (i = directory_list_offset - 1; i >= 0; i--)
503 if (vflag >= 2)
504 printf ("\n%2d %s", directory_list_offset - (int)i, DIRSTACK_ENTRY (i));
505 else
506 printf ("%s%s", (vflag & 1) ? "\n" : " ", DIRSTACK_ENTRY (i));
507
508 putchar ('\n');
509
510 return (sh_chkwrite (EXECUTION_SUCCESS));
511 }
512
513 static void
514 pushd_error (offset, arg)
515 int offset;
516 char *arg;
517 {
518 if (offset == 0)
519 builtin_error (_("directory stack empty"));
520 else
521 sh_erange (arg, _("directory stack index"));
522 }
523
524 static void
525 clear_directory_stack ()
526 {
527 register int i;
528
529 for (i = 0; i < directory_list_offset; i++)
530 free (pushd_directory_list[i]);
531 directory_list_offset = 0;
532 }
533
534 /* Switch to the directory in NAME. This uses the cd_builtin to do the work,
535 so if the result is EXECUTION_FAILURE then an error message has already
536 been printed. */
537 static int
538 cd_to_string (name)
539 char *name;
540 {
541 WORD_LIST *tlist;
542 WORD_LIST *dir;
543 int result;
544
545 dir = make_word_list (make_word (name), NULL);
546 tlist = make_word_list (make_word ("--"), dir);
547 result = cd_builtin (tlist);
548 dispose_words (tlist);
549 return (result);
550 }
551
552 static int
553 change_to_temp (temp)
554 char *temp;
555 {
556 int tt;
557
558 tt = temp ? cd_to_string (temp) : EXECUTION_FAILURE;
559
560 if (tt == EXECUTION_SUCCESS)
561 dirs_builtin ((WORD_LIST *)NULL);
562
563 return (tt);
564 }
565
566 static void
567 add_dirstack_element (dir)
568 char *dir;
569 {
570 if (directory_list_offset == directory_list_size)
571 pushd_directory_list = strvec_resize (pushd_directory_list, directory_list_size += 10);
572 pushd_directory_list[directory_list_offset++] = dir;
573 }
574
575 static int
576 get_dirstack_index (ind, sign, indexp)
577 intmax_t ind;
578 int sign, *indexp;
579 {
580 if (indexp)
581 *indexp = sign > 0 ? 1 : 2;
582
583 /* dirs +0 prints the current working directory. */
584 /* dirs -0 prints last element in directory stack */
585 if (ind == 0 && sign > 0)
586 return 0;
587 else if (ind == directory_list_offset)
588 {
589 if (indexp)
590 *indexp = sign > 0 ? 2 : 1;
591 return 0;
592 }
593 else if (ind >= 0 && ind <= directory_list_offset)
594 return (sign > 0 ? directory_list_offset - ind : ind);
595 else
596 return -1;
597 }
598
599 /* Used by the tilde expansion code. */
600 char *
601 get_dirstack_from_string (string)
602 char *string;
603 {
604 int ind, sign, index_flag;
605 intmax_t i;
606
607 sign = 1;
608 if (*string == '-' || *string == '+')
609 {
610 sign = (*string == '-') ? -1 : 1;
611 string++;
612 }
613 if (legal_number (string, &i) == 0)
614 return ((char *)NULL);
615
616 index_flag = 0;
617 ind = get_dirstack_index (i, sign, &index_flag);
618 if (index_flag && (ind < 0 || ind > directory_list_offset))
619 return ((char *)NULL);
620 if (index_flag == 0 || (index_flag == 1 && ind == 0))
621 return (get_string_value ("PWD"));
622 else
623 return (pushd_directory_list[ind]);
624 }
625
626 #ifdef INCLUDE_UNUSED
627 char *
628 get_dirstack_element (ind, sign)
629 intmax_t ind;
630 int sign;
631 {
632 int i;
633
634 i = get_dirstack_index (ind, sign, (int *)NULL);
635 return (i < 0 || i > directory_list_offset) ? (char *)NULL
636 : pushd_directory_list[i];
637 }
638 #endif
639
640 void
641 set_dirstack_element (ind, sign, value)
642 intmax_t ind;
643 int sign;
644 char *value;
645 {
646 int i;
647
648 i = get_dirstack_index (ind, sign, (int *)NULL);
649 if (ind == 0 || i < 0 || i > directory_list_offset)
650 return;
651 free (pushd_directory_list[i]);
652 pushd_directory_list[i] = savestring (value);
653 }
654
655 WORD_LIST *
656 get_directory_stack (flags)
657 int flags;
658 {
659 register int i;
660 WORD_LIST *ret;
661 char *d, *t;
662
663 for (ret = (WORD_LIST *)NULL, i = 0; i < directory_list_offset; i++)
664 {
665 d = (flags&1) ? polite_directory_format (pushd_directory_list[i])
666 : pushd_directory_list[i];
667 ret = make_word_list (make_word (d), ret);
668 }
669 /* Now the current directory. */
670 d = get_working_directory ("dirstack");
671 i = 0; /* sentinel to decide whether or not to free d */
672 if (d == 0)
673 d = ".";
674 else
675 {
676 t = polite_directory_format (d);
677 /* polite_directory_format sometimes returns its argument unchanged.
678 If it does not, we can free d right away. If it does, we need to
679 mark d to be deleted later. */
680 if (t != d)
681 {
682 free (d);
683 d = t;
684 }
685 else /* t == d, so d is what we want */
686 i = 1;
687 }
688 ret = make_word_list (make_word (d), ret);
689 if (i)
690 free (d);
691 return ret; /* was (REVERSE_LIST (ret, (WORD_LIST *)); */
692 }
693
694 #ifdef LOADABLE_BUILTIN
695 char * const dirs_doc[] = {
696 N_("Display the list of currently remembered directories. Directories\n\
697 find their way onto the list with the `pushd' command; you can get\n\
698 back up through the list with the `popd' command.\n\
699 \n\
700 Options:\n\
701 -c clear the directory stack by deleting all of the elements\n\
702 -l do not print tilde-prefixed versions of directories relative\n\
703 to your home directory\n\
704 -p print the directory stack with one entry per line\n\
705 -v print the directory stack with one entry per line prefixed\n\
706 with its position in the stack\n\
707 \n\
708 Arguments:\n\
709 +N Displays the Nth entry counting from the left of the list shown by\n\
710 dirs when invoked without options, starting with zero.\n\
711 \n\
712 -N Displays the Nth entry counting from the right of the list shown by\n\
713 dirs when invoked without options, starting with zero."),
714 (char *)NULL
715 };
716
717 char * const pushd_doc[] = {
718 N_("Adds a directory to the top of the directory stack, or rotates\n\
719 the stack, making the new top of the stack the current working\n\
720 directory. With no arguments, exchanges the top two directories.\n\
721 \n\
722 Options:\n\
723 -n Suppresses the normal change of directory when adding\n\
724 directories to the stack, so only the stack is manipulated.\n\
725 \n\
726 Arguments:\n\
727 +N Rotates the stack so that the Nth directory (counting\n\
728 from the left of the list shown by `dirs', starting with\n\
729 zero) is at the top.\n\
730 \n\
731 -N Rotates the stack so that the Nth directory (counting\n\
732 from the right of the list shown by `dirs', starting with\n\
733 zero) is at the top.\n\
734 \n\
735 dir Adds DIR to the directory stack at the top, making it the\n\
736 new current working directory.\n\
737 \n\
738 The `dirs' builtin displays the directory stack."),
739 (char *)NULL
740 };
741
742 char * const popd_doc[] = {
743 N_("Removes entries from the directory stack. With no arguments, removes\n\
744 the top directory from the stack, and changes to the new top directory.\n\
745 \n\
746 Options:\n\
747 -n Suppresses the normal change of directory when removing\n\
748 directories from the stack, so only the stack is manipulated.\n\
749 \n\
750 Arguments:\n\
751 +N Removes the Nth entry counting from the left of the list\n\
752 shown by `dirs', starting with zero. For example: `popd +0'\n\
753 removes the first directory, `popd +1' the second.\n\
754 \n\
755 -N Removes the Nth entry counting from the right of the list\n\
756 shown by `dirs', starting with zero. For example: `popd -0'\n\
757 removes the last directory, `popd -1' the next to last.\n\
758 \n\
759 The `dirs' builtin displays the directory stack."),
760 (char *)NULL
761 };
762
763 struct builtin pushd_struct = {
764 "pushd",
765 pushd_builtin,
766 BUILTIN_ENABLED,
767 pushd_doc,
768 "pushd [+N | -N] [-n] [dir]",
769 0
770 };
771
772 struct builtin popd_struct = {
773 "popd",
774 popd_builtin,
775 BUILTIN_ENABLED,
776 popd_doc,
777 "popd [+N | -N] [-n]",
778 0
779 };
780
781 struct builtin dirs_struct = {
782 "dirs",
783 dirs_builtin,
784 BUILTIN_ENABLED,
785 dirs_doc,
786 "dirs [-clpv] [+N] [-N]",
787 0
788 };
789 #endif /* LOADABLE_BUILTIN */
790
791 #endif /* PUSHD_AND_POPD */