| This file is pushd.def, from which is created pushd.c. It implements the |
| builtins "pushd", "popd", and "dirs" in Bash. |
| |
| Copyright (C) 1987-2013 Free Software Foundation, Inc. |
| |
| This file is part of GNU Bash, the Bourne Again SHell. |
| |
| Bash is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| Bash is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with Bash. If not, see <http://www.gnu.org/licenses/>. |
| |
| $PRODUCES pushd.c |
| |
| $BUILTIN pushd |
| $FUNCTION pushd_builtin |
| $DEPENDS_ON PUSHD_AND_POPD |
| $SHORT_DOC pushd [-n] [+N | -N | dir] |
| Add directories to stack. |
| |
| Adds a directory to the top of the directory stack, or rotates |
| the stack, making the new top of the stack the current working |
| directory. With no arguments, exchanges the top two directories. |
| |
| Options: |
| -n Suppresses the normal change of directory when adding |
| directories to the stack, so only the stack is manipulated. |
| |
| Arguments: |
| +N Rotates the stack so that the Nth directory (counting |
| from the left of the list shown by `dirs', starting with |
| zero) is at the top. |
| |
| -N Rotates the stack so that the Nth directory (counting |
| from the right of the list shown by `dirs', starting with |
| zero) is at the top. |
| |
| dir Adds DIR to the directory stack at the top, making it the |
| new current working directory. |
| |
| The `dirs' builtin displays the directory stack. |
| |
| Exit Status: |
| Returns success unless an invalid argument is supplied or the directory |
| change fails. |
| $END |
| |
| $BUILTIN popd |
| $FUNCTION popd_builtin |
| $DEPENDS_ON PUSHD_AND_POPD |
| $SHORT_DOC popd [-n] [+N | -N] |
| Remove directories from stack. |
| |
| Removes entries from the directory stack. With no arguments, removes |
| the top directory from the stack, and changes to the new top directory. |
| |
| Options: |
| -n Suppresses the normal change of directory when removing |
| directories from the stack, so only the stack is manipulated. |
| |
| Arguments: |
| +N Removes the Nth entry counting from the left of the list |
| shown by `dirs', starting with zero. For example: `popd +0' |
| removes the first directory, `popd +1' the second. |
| |
| -N Removes the Nth entry counting from the right of the list |
| shown by `dirs', starting with zero. For example: `popd -0' |
| removes the last directory, `popd -1' the next to last. |
| |
| The `dirs' builtin displays the directory stack. |
| |
| Exit Status: |
| Returns success unless an invalid argument is supplied or the directory |
| change fails. |
| $END |
| |
| $BUILTIN dirs |
| $FUNCTION dirs_builtin |
| $DEPENDS_ON PUSHD_AND_POPD |
| $SHORT_DOC dirs [-clpv] [+N] [-N] |
| Display directory stack. |
| |
| Display the list of currently remembered directories. Directories |
| find their way onto the list with the `pushd' command; you can get |
| back up through the list with the `popd' command. |
| |
| Options: |
| -c clear the directory stack by deleting all of the elements |
| -l do not print tilde-prefixed versions of directories relative |
| to your home directory |
| -p print the directory stack with one entry per line |
| -v print the directory stack with one entry per line prefixed |
| with its position in the stack |
| |
| Arguments: |
| +N Displays the Nth entry counting from the left of the list shown by |
| dirs when invoked without options, starting with zero. |
| |
| -N Displays the Nth entry counting from the right of the list shown by |
| dirs when invoked without options, starting with zero. |
| |
| Exit Status: |
| Returns success unless an invalid option is supplied or an error occurs. |
| $END |
| |
| #include <config.h> |
| |
| #if defined (PUSHD_AND_POPD) |
| #include <stdio.h> |
| #if defined (HAVE_SYS_PARAM_H) |
| # include <sys/param.h> |
| #endif |
| |
| #if defined (HAVE_UNISTD_H) |
| # ifdef _MINIX |
| # include <sys/types.h> |
| # endif |
| # include <unistd.h> |
| #endif |
| |
| #include "../bashansi.h" |
| #include "../bashintl.h" |
| |
| #include <errno.h> |
| |
| #include <tilde/tilde.h> |
| |
| #include "../shell.h" |
| #include "maxpath.h" |
| #include "common.h" |
| #include "builtext.h" |
| |
| #ifdef LOADABLE_BUILTIN |
| # include "builtins.h" |
| #endif |
| |
| #if !defined (errno) |
| #include <errno.h> |
| #endif /* !errno */ |
| |
| /* The list of remembered directories. */ |
| static char **pushd_directory_list = (char **)NULL; |
| |
| /* Number of existing slots in this list. */ |
| static int directory_list_size; |
| |
| /* Offset to the end of the list. */ |
| static int directory_list_offset; |
| |
| static void pushd_error __P((int, char *)); |
| static void clear_directory_stack __P((void)); |
| static int cd_to_string __P((char *)); |
| static int change_to_temp __P((char *)); |
| static void add_dirstack_element __P((char *)); |
| static int get_dirstack_index __P((intmax_t, int, int *)); |
| |
| #define NOCD 0x01 |
| #define ROTATE 0x02 |
| #define LONGFORM 0x04 |
| #define CLEARSTAK 0x08 |
| |
| int |
| pushd_builtin (list) |
| WORD_LIST *list; |
| { |
| WORD_LIST *orig_list; |
| char *temp, *current_directory, *top; |
| int j, flags, skipopt; |
| intmax_t num; |
| char direction; |
| |
| orig_list = list; |
| if (list && list->word && ISOPTION (list->word->word, '-')) |
| { |
| list = list->next; |
| skipopt = 1; |
| } |
| else |
| skipopt = 0; |
| |
| /* If there is no argument list then switch current and |
| top of list. */ |
| if (list == 0) |
| { |
| if (directory_list_offset == 0) |
| { |
| builtin_error (_("no other directory")); |
| return (EXECUTION_FAILURE); |
| } |
| |
| current_directory = get_working_directory ("pushd"); |
| if (current_directory == 0) |
| return (EXECUTION_FAILURE); |
| |
| j = directory_list_offset - 1; |
| temp = pushd_directory_list[j]; |
| pushd_directory_list[j] = current_directory; |
| j = change_to_temp (temp); |
| free (temp); |
| return j; |
| } |
| |
| for (flags = 0; skipopt == 0 && list; list = list->next) |
| { |
| if (ISOPTION (list->word->word, 'n')) |
| { |
| flags |= NOCD; |
| } |
| else if (ISOPTION (list->word->word, '-')) |
| { |
| list = list->next; |
| break; |
| } |
| else if (list->word->word[0] == '-' && list->word->word[1] == '\0') |
| /* Let `pushd -' work like it used to. */ |
| break; |
| else if (((direction = list->word->word[0]) == '+') || direction == '-') |
| { |
| if (legal_number (list->word->word + 1, &num) == 0) |
| { |
| sh_invalidnum (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| |
| if (direction == '-') |
| num = directory_list_offset - num; |
| |
| if (num > directory_list_offset || num < 0) |
| { |
| pushd_error (directory_list_offset, list->word->word); |
| return (EXECUTION_FAILURE); |
| } |
| flags |= ROTATE; |
| } |
| else if (*list->word->word == '-') |
| { |
| sh_invalidopt (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| else |
| break; |
| } |
| |
| if (flags & ROTATE) |
| { |
| /* Rotate the stack num times. Remember, the current |
| directory acts like it is part of the stack. */ |
| temp = get_working_directory ("pushd"); |
| |
| if (num == 0) |
| { |
| j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; |
| free (temp); |
| return j; |
| } |
| |
| do |
| { |
| top = pushd_directory_list[directory_list_offset - 1]; |
| |
| for (j = directory_list_offset - 2; j > -1; j--) |
| pushd_directory_list[j + 1] = pushd_directory_list[j]; |
| |
| pushd_directory_list[j + 1] = temp; |
| |
| temp = top; |
| num--; |
| } |
| while (num); |
| |
| j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; |
| free (temp); |
| return j; |
| } |
| |
| if (list == 0) |
| return (EXECUTION_SUCCESS); |
| |
| /* Change to the directory in list->word->word. Save the current |
| directory on the top of the stack. */ |
| current_directory = get_working_directory ("pushd"); |
| if (current_directory == 0) |
| return (EXECUTION_FAILURE); |
| |
| j = ((flags & NOCD) == 0) ? cd_builtin (skipopt ? orig_list : list) : EXECUTION_SUCCESS; |
| if (j == EXECUTION_SUCCESS) |
| { |
| add_dirstack_element ((flags & NOCD) ? savestring (list->word->word) : current_directory); |
| dirs_builtin ((WORD_LIST *)NULL); |
| if (flags & NOCD) |
| free (current_directory); |
| return (EXECUTION_SUCCESS); |
| } |
| else |
| { |
| free (current_directory); |
| return (EXECUTION_FAILURE); |
| } |
| } |
| |
| /* Pop the directory stack, and then change to the new top of the stack. |
| If LIST is non-null it should consist of a word +N or -N, which says |
| what element to delete from the stack. The default is the top one. */ |
| int |
| popd_builtin (list) |
| WORD_LIST *list; |
| { |
| register int i; |
| intmax_t which; |
| int flags; |
| char direction; |
| char *which_word; |
| |
| which_word = (char *)NULL; |
| for (flags = 0, which = 0, direction = '+'; list; list = list->next) |
| { |
| if (ISOPTION (list->word->word, 'n')) |
| { |
| flags |= NOCD; |
| } |
| else if (ISOPTION (list->word->word, '-')) |
| { |
| list = list->next; |
| break; |
| } |
| else if (((direction = list->word->word[0]) == '+') || direction == '-') |
| { |
| if (legal_number (list->word->word + 1, &which) == 0) |
| { |
| sh_invalidnum (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| which_word = list->word->word; |
| } |
| else if (*list->word->word == '-') |
| { |
| sh_invalidopt (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| else if (*list->word->word) |
| { |
| builtin_error (_("%s: invalid argument"), list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| else |
| break; |
| } |
| |
| if (which > directory_list_offset || (directory_list_offset == 0 && which == 0)) |
| { |
| pushd_error (directory_list_offset, which_word ? which_word : ""); |
| return (EXECUTION_FAILURE); |
| } |
| |
| /* Handle case of no specification, or top of stack specification. */ |
| if ((direction == '+' && which == 0) || |
| (direction == '-' && which == directory_list_offset)) |
| { |
| i = ((flags & NOCD) == 0) ? cd_to_string (pushd_directory_list[directory_list_offset - 1]) |
| : EXECUTION_SUCCESS; |
| if (i != EXECUTION_SUCCESS) |
| return (i); |
| free (pushd_directory_list[--directory_list_offset]); |
| } |
| else |
| { |
| /* Since an offset other than the top directory was specified, |
| remove that directory from the list and shift the remainder |
| of the list into place. */ |
| i = (direction == '+') ? directory_list_offset - which : which; |
| free (pushd_directory_list[i]); |
| directory_list_offset--; |
| |
| /* Shift the remainder of the list into place. */ |
| for (; i < directory_list_offset; i++) |
| pushd_directory_list[i] = pushd_directory_list[i + 1]; |
| } |
| |
| dirs_builtin ((WORD_LIST *)NULL); |
| return (EXECUTION_SUCCESS); |
| } |
| |
| /* Print the current list of directories on the directory stack. */ |
| int |
| dirs_builtin (list) |
| WORD_LIST *list; |
| { |
| int flags, desired_index, index_flag, vflag; |
| intmax_t i; |
| char *temp, *w; |
| |
| for (flags = vflag = index_flag = 0, desired_index = -1, w = ""; list; list = list->next) |
| { |
| if (ISOPTION (list->word->word, 'l')) |
| { |
| flags |= LONGFORM; |
| } |
| else if (ISOPTION (list->word->word, 'c')) |
| { |
| flags |= CLEARSTAK; |
| } |
| else if (ISOPTION (list->word->word, 'v')) |
| { |
| vflag |= 2; |
| } |
| else if (ISOPTION (list->word->word, 'p')) |
| { |
| vflag |= 1; |
| } |
| else if (ISOPTION (list->word->word, '-')) |
| { |
| list = list->next; |
| break; |
| } |
| else if (*list->word->word == '+' || *list->word->word == '-') |
| { |
| int sign; |
| if (legal_number (w = list->word->word + 1, &i) == 0) |
| { |
| sh_invalidnum (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| sign = (*list->word->word == '+') ? 1 : -1; |
| desired_index = get_dirstack_index (i, sign, &index_flag); |
| } |
| else |
| { |
| sh_invalidopt (list->word->word); |
| builtin_usage (); |
| return (EX_USAGE); |
| } |
| } |
| |
| if (flags & CLEARSTAK) |
| { |
| clear_directory_stack (); |
| return (EXECUTION_SUCCESS); |
| } |
| |
| if (index_flag && (desired_index < 0 || desired_index > directory_list_offset)) |
| { |
| pushd_error (directory_list_offset, w); |
| return (EXECUTION_FAILURE); |
| } |
| |
| #define DIRSTACK_FORMAT(temp) \ |
| (flags & LONGFORM) ? temp : polite_directory_format (temp) |
| |
| /* The first directory printed is always the current working directory. */ |
| if (index_flag == 0 || (index_flag == 1 && desired_index == 0)) |
| { |
| temp = get_working_directory ("dirs"); |
| if (temp == 0) |
| temp = savestring (_("<no current directory>")); |
| if (vflag & 2) |
| printf ("%2d %s", 0, DIRSTACK_FORMAT (temp)); |
| else |
| printf ("%s", DIRSTACK_FORMAT (temp)); |
| free (temp); |
| if (index_flag) |
| { |
| putchar ('\n'); |
| return (sh_chkwrite (EXECUTION_SUCCESS)); |
| } |
| } |
| |
| #define DIRSTACK_ENTRY(i) \ |
| (flags & LONGFORM) ? pushd_directory_list[i] \ |
| : polite_directory_format (pushd_directory_list[i]) |
| |
| /* Now print the requested directory stack entries. */ |
| if (index_flag) |
| { |
| if (vflag & 2) |
| printf ("%2d %s", directory_list_offset - desired_index, |
| DIRSTACK_ENTRY (desired_index)); |
| else |
| printf ("%s", DIRSTACK_ENTRY (desired_index)); |
| } |
| else |
| for (i = directory_list_offset - 1; i >= 0; i--) |
| if (vflag >= 2) |
| printf ("\n%2d %s", directory_list_offset - (int)i, DIRSTACK_ENTRY (i)); |
| else |
| printf ("%s%s", (vflag & 1) ? "\n" : " ", DIRSTACK_ENTRY (i)); |
| |
| putchar ('\n'); |
| |
| return (sh_chkwrite (EXECUTION_SUCCESS)); |
| } |
| |
| static void |
| pushd_error (offset, arg) |
| int offset; |
| char *arg; |
| { |
| if (offset == 0) |
| builtin_error (_("directory stack empty")); |
| else |
| sh_erange (arg, _("directory stack index")); |
| } |
| |
| static void |
| clear_directory_stack () |
| { |
| register int i; |
| |
| for (i = 0; i < directory_list_offset; i++) |
| free (pushd_directory_list[i]); |
| directory_list_offset = 0; |
| } |
| |
| /* Switch to the directory in NAME. This uses the cd_builtin to do the work, |
| so if the result is EXECUTION_FAILURE then an error message has already |
| been printed. */ |
| static int |
| cd_to_string (name) |
| char *name; |
| { |
| WORD_LIST *tlist; |
| WORD_LIST *dir; |
| int result; |
| |
| dir = make_word_list (make_word (name), NULL); |
| tlist = make_word_list (make_word ("--"), dir); |
| result = cd_builtin (tlist); |
| dispose_words (tlist); |
| return (result); |
| } |
| |
| static int |
| change_to_temp (temp) |
| char *temp; |
| { |
| int tt; |
| |
| tt = temp ? cd_to_string (temp) : EXECUTION_FAILURE; |
| |
| if (tt == EXECUTION_SUCCESS) |
| dirs_builtin ((WORD_LIST *)NULL); |
| |
| return (tt); |
| } |
| |
| static void |
| add_dirstack_element (dir) |
| char *dir; |
| { |
| if (directory_list_offset == directory_list_size) |
| pushd_directory_list = strvec_resize (pushd_directory_list, directory_list_size += 10); |
| pushd_directory_list[directory_list_offset++] = dir; |
| } |
| |
| static int |
| get_dirstack_index (ind, sign, indexp) |
| intmax_t ind; |
| int sign, *indexp; |
| { |
| if (indexp) |
| *indexp = sign > 0 ? 1 : 2; |
| |
| /* dirs +0 prints the current working directory. */ |
| /* dirs -0 prints last element in directory stack */ |
| if (ind == 0 && sign > 0) |
| return 0; |
| else if (ind == directory_list_offset) |
| { |
| if (indexp) |
| *indexp = sign > 0 ? 2 : 1; |
| return 0; |
| } |
| else if (ind >= 0 && ind <= directory_list_offset) |
| return (sign > 0 ? directory_list_offset - ind : ind); |
| else |
| return -1; |
| } |
| |
| /* Used by the tilde expansion code. */ |
| char * |
| get_dirstack_from_string (string) |
| char *string; |
| { |
| int ind, sign, index_flag; |
| intmax_t i; |
| |
| sign = 1; |
| if (*string == '-' || *string == '+') |
| { |
| sign = (*string == '-') ? -1 : 1; |
| string++; |
| } |
| if (legal_number (string, &i) == 0) |
| return ((char *)NULL); |
| |
| index_flag = 0; |
| ind = get_dirstack_index (i, sign, &index_flag); |
| if (index_flag && (ind < 0 || ind > directory_list_offset)) |
| return ((char *)NULL); |
| if (index_flag == 0 || (index_flag == 1 && ind == 0)) |
| return (get_string_value ("PWD")); |
| else |
| return (pushd_directory_list[ind]); |
| } |
| |
| #ifdef INCLUDE_UNUSED |
| char * |
| get_dirstack_element (ind, sign) |
| intmax_t ind; |
| int sign; |
| { |
| int i; |
| |
| i = get_dirstack_index (ind, sign, (int *)NULL); |
| return (i < 0 || i > directory_list_offset) ? (char *)NULL |
| : pushd_directory_list[i]; |
| } |
| #endif |
| |
| void |
| set_dirstack_element (ind, sign, value) |
| intmax_t ind; |
| int sign; |
| char *value; |
| { |
| int i; |
| |
| i = get_dirstack_index (ind, sign, (int *)NULL); |
| if (ind == 0 || i < 0 || i > directory_list_offset) |
| return; |
| free (pushd_directory_list[i]); |
| pushd_directory_list[i] = savestring (value); |
| } |
| |
| WORD_LIST * |
| get_directory_stack (flags) |
| int flags; |
| { |
| register int i; |
| WORD_LIST *ret; |
| char *d, *t; |
| |
| for (ret = (WORD_LIST *)NULL, i = 0; i < directory_list_offset; i++) |
| { |
| d = (flags&1) ? polite_directory_format (pushd_directory_list[i]) |
| : pushd_directory_list[i]; |
| ret = make_word_list (make_word (d), ret); |
| } |
| /* Now the current directory. */ |
| d = get_working_directory ("dirstack"); |
| i = 0; /* sentinel to decide whether or not to free d */ |
| if (d == 0) |
| d = "."; |
| else |
| { |
| t = polite_directory_format (d); |
| /* polite_directory_format sometimes returns its argument unchanged. |
| If it does not, we can free d right away. If it does, we need to |
| mark d to be deleted later. */ |
| if (t != d) |
| { |
| free (d); |
| d = t; |
| } |
| else /* t == d, so d is what we want */ |
| i = 1; |
| } |
| ret = make_word_list (make_word (d), ret); |
| if (i) |
| free (d); |
| return ret; /* was (REVERSE_LIST (ret, (WORD_LIST *)); */ |
| } |
| |
| #ifdef LOADABLE_BUILTIN |
| char * const dirs_doc[] = { |
| N_("Display the list of currently remembered directories. Directories\n\ |
| find their way onto the list with the `pushd' command; you can get\n\ |
| back up through the list with the `popd' command.\n\ |
| \n\ |
| Options:\n\ |
| -c clear the directory stack by deleting all of the elements\n\ |
| -l do not print tilde-prefixed versions of directories relative\n\ |
| to your home directory\n\ |
| -p print the directory stack with one entry per line\n\ |
| -v print the directory stack with one entry per line prefixed\n\ |
| with its position in the stack\n\ |
| \n\ |
| Arguments:\n\ |
| +N Displays the Nth entry counting from the left of the list shown by\n\ |
| dirs when invoked without options, starting with zero.\n\ |
| \n\ |
| -N Displays the Nth entry counting from the right of the list shown by\n\ |
| dirs when invoked without options, starting with zero."), |
| (char *)NULL |
| }; |
| |
| char * const pushd_doc[] = { |
| N_("Adds a directory to the top of the directory stack, or rotates\n\ |
| the stack, making the new top of the stack the current working\n\ |
| directory. With no arguments, exchanges the top two directories.\n\ |
| \n\ |
| Options:\n\ |
| -n Suppresses the normal change of directory when adding\n\ |
| directories to the stack, so only the stack is manipulated.\n\ |
| \n\ |
| Arguments:\n\ |
| +N Rotates the stack so that the Nth directory (counting\n\ |
| from the left of the list shown by `dirs', starting with\n\ |
| zero) is at the top.\n\ |
| \n\ |
| -N Rotates the stack so that the Nth directory (counting\n\ |
| from the right of the list shown by `dirs', starting with\n\ |
| zero) is at the top.\n\ |
| \n\ |
| dir Adds DIR to the directory stack at the top, making it the\n\ |
| new current working directory.\n\ |
| \n\ |
| The `dirs' builtin displays the directory stack."), |
| (char *)NULL |
| }; |
| |
| char * const popd_doc[] = { |
| N_("Removes entries from the directory stack. With no arguments, removes\n\ |
| the top directory from the stack, and changes to the new top directory.\n\ |
| \n\ |
| Options:\n\ |
| -n Suppresses the normal change of directory when removing\n\ |
| directories from the stack, so only the stack is manipulated.\n\ |
| \n\ |
| Arguments:\n\ |
| +N Removes the Nth entry counting from the left of the list\n\ |
| shown by `dirs', starting with zero. For example: `popd +0'\n\ |
| removes the first directory, `popd +1' the second.\n\ |
| \n\ |
| -N Removes the Nth entry counting from the right of the list\n\ |
| shown by `dirs', starting with zero. For example: `popd -0'\n\ |
| removes the last directory, `popd -1' the next to last.\n\ |
| \n\ |
| The `dirs' builtin displays the directory stack."), |
| (char *)NULL |
| }; |
| |
| struct builtin pushd_struct = { |
| "pushd", |
| pushd_builtin, |
| BUILTIN_ENABLED, |
| pushd_doc, |
| "pushd [+N | -N] [-n] [dir]", |
| 0 |
| }; |
| |
| struct builtin popd_struct = { |
| "popd", |
| popd_builtin, |
| BUILTIN_ENABLED, |
| popd_doc, |
| "popd [+N | -N] [-n]", |
| 0 |
| }; |
| |
| struct builtin dirs_struct = { |
| "dirs", |
| dirs_builtin, |
| BUILTIN_ENABLED, |
| dirs_doc, |
| "dirs [-clpv] [+N] [-N]", |
| 0 |
| }; |
| #endif /* LOADABLE_BUILTIN */ |
| |
| #endif /* PUSHD_AND_POPD */ |