mirror of
https://https.git.savannah.gnu.org/git/bash.git
synced 2026-07-06 11:50:49 +02:00
690 lines
16 KiB
Modula-2
690 lines
16 KiB
Modula-2
This file is cd.def, from which is created cd.c. It implements the
|
|
builtins "cd", "pwd", "pushd", "popd", and "dirs" in Bash.
|
|
|
|
Copyright (C) 1987, 1989, 1991 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 1, 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; see the file COPYING. If not, write to the Free Software
|
|
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
$PRODUCES cd.c
|
|
|
|
#include <stdio.h>
|
|
#include <sys/param.h>
|
|
|
|
#if defined (HAVE_STRING_H)
|
|
# include <string.h>
|
|
#else /* !HAVE_STRING_H */
|
|
# include <strings.h>
|
|
#endif /* !HAVE_STRING_H */
|
|
|
|
#include <errno.h>
|
|
#include <tilde/tilde.h>
|
|
|
|
#include "../shell.h"
|
|
#include "../flags.h"
|
|
#include "../maxpath.h"
|
|
#include "common.h"
|
|
|
|
#if !defined (errno)
|
|
extern int errno;
|
|
#endif /* !errno */
|
|
|
|
static int change_to_directory (), cd_to_string ();
|
|
|
|
$BUILTIN cd
|
|
$FUNCTION cd_builtin
|
|
$SHORT_DOC cd [dir]
|
|
Change the current directory to DIR. The variable $HOME is the
|
|
default DIR. The variable $CDPATH defines the search path for
|
|
the directory containing DIR. Alternative directory names are
|
|
separated by a colon (:). A null directory name is the same as
|
|
the current directory, i.e. `.'. If DIR begins with a slash (/),
|
|
then $CDPATH is not used. If the directory is not found, and the
|
|
shell variable `cdable_vars' exists, then try the word as a variable
|
|
name. If that variable has a value, then cd to the value of that
|
|
variable.
|
|
$END
|
|
|
|
/* This builtin is ultimately the way that all user-visible commands should
|
|
change the current working directory. It is called by cd_to_string (),
|
|
so the programming interface is simple, and it handles errors and
|
|
restrictions properly. */
|
|
int
|
|
cd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *dirname;
|
|
|
|
#if defined (RESTRICTED_SHELL)
|
|
if (restricted)
|
|
{
|
|
builtin_error ("restricted");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
#endif /* RESTRICTED_SHELL */
|
|
|
|
if (list)
|
|
{
|
|
char *extract_colon_unit ();
|
|
char *path_string = get_string_value ("CDPATH");
|
|
char *path;
|
|
int path_index = 0, dirlen, pathlen;
|
|
|
|
dirname = list->word->word;
|
|
|
|
if (path_string && !absolute_pathname (dirname))
|
|
{
|
|
while ((path = extract_colon_unit (path_string, &path_index)))
|
|
{
|
|
char *dir;
|
|
|
|
if (*path == '~')
|
|
{
|
|
char *te_string = tilde_expand (path);
|
|
|
|
free (path);
|
|
path = te_string;
|
|
}
|
|
|
|
if (!*path)
|
|
{
|
|
free (path);
|
|
path = xmalloc (2);
|
|
path[0] = '.'; /* by definition. */
|
|
path[1] = '\0';
|
|
}
|
|
|
|
dirlen = strlen (dirname);
|
|
pathlen = strlen (path);
|
|
dir = xmalloc (2 + dirlen + pathlen);
|
|
strcpy (dir, path);
|
|
if (path[pathlen - 1] != '/')
|
|
{
|
|
dir[pathlen++] = '/';
|
|
dir[pathlen] = '\0';
|
|
}
|
|
strcpy (dir + pathlen, dirname);
|
|
free (path);
|
|
|
|
if (change_to_directory (dir))
|
|
{
|
|
/* replaces (strncmp (dir, "./", 2) != 0) */
|
|
if (dir[0] != '.' || dir[1] != '/')
|
|
printf ("%s\n", dir);
|
|
|
|
free (dir);
|
|
goto bind_and_exit;
|
|
}
|
|
else
|
|
free (dir);
|
|
}
|
|
}
|
|
|
|
if (!change_to_directory (dirname))
|
|
{
|
|
/* Maybe this is `cd -', equivalent to `cd $OLDPWD' */
|
|
if (dirname[0] == '-' && dirname[1] == '\0')
|
|
{
|
|
char *t = get_string_value ("OLDPWD");
|
|
|
|
if (t && change_to_directory (t))
|
|
goto bind_and_exit;
|
|
}
|
|
|
|
/* If the user requests it, then perhaps this is the name of
|
|
a shell variable, whose value contains the directory to
|
|
change to. If that is the case, then change to that
|
|
directory. */
|
|
if (find_variable ("cdable_vars"))
|
|
{
|
|
char *t = get_string_value (dirname);
|
|
|
|
if (t && change_to_directory (t))
|
|
{
|
|
printf ("%s\n", t);
|
|
goto bind_and_exit;
|
|
}
|
|
}
|
|
|
|
file_error (dirname);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
goto bind_and_exit;
|
|
}
|
|
else
|
|
{
|
|
dirname = get_string_value ("HOME");
|
|
|
|
if (!dirname)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
if (!change_to_directory (dirname))
|
|
{
|
|
file_error (dirname);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
bind_and_exit:
|
|
{
|
|
char *directory;
|
|
|
|
directory = get_working_directory ("cd");
|
|
|
|
bind_variable ("OLDPWD", get_string_value ("PWD"));
|
|
bind_variable ("PWD", directory);
|
|
|
|
FREE (directory);
|
|
}
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
}
|
|
|
|
$BUILTIN pwd
|
|
$FUNCTION pwd_builtin
|
|
$SHORT_DOC pwd
|
|
Print the current working directory.
|
|
$END
|
|
|
|
/* Non-zero means that pwd always give verbatim directory, regardless of
|
|
symbolic link following. */
|
|
static int verbatim_pwd;
|
|
|
|
/* Print the name of the current working directory. */
|
|
pwd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *directory, *s;
|
|
|
|
#if 0
|
|
no_args (list);
|
|
#else
|
|
verbatim_pwd = no_symbolic_links;
|
|
if (list && (s = list->word->word) && s[0] == '-' && s[1] == 'P' && !s[2])
|
|
verbatim_pwd = 1;
|
|
#endif
|
|
|
|
if (verbatim_pwd)
|
|
{
|
|
char *buffer = xmalloc (MAXPATHLEN);
|
|
directory = getwd (buffer);
|
|
|
|
if (!directory)
|
|
{
|
|
builtin_error ("%s", buffer);
|
|
free (buffer);
|
|
}
|
|
}
|
|
else
|
|
directory = get_working_directory ("pwd");
|
|
|
|
if (directory)
|
|
{
|
|
printf ("%s\n", directory);
|
|
fflush (stdout);
|
|
free (directory);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
$BUILTIN pushd
|
|
$FUNCTION pushd_builtin
|
|
$DEPENDS_ON PUSHD_AND_POPD
|
|
$SHORT_DOC pushd [dir | +n | -n]
|
|
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.
|
|
|
|
+n Rotates the stack so that the Nth directory (counting
|
|
from the left of the list shown by `dirs') is at the top.
|
|
|
|
-n Rotates the stack so that the Nth directory (counting
|
|
from the right) is at the top.
|
|
|
|
dir adds DIR to the directory stack at the top, making it the
|
|
new current working directory.
|
|
|
|
You can see the directory stack with the `dirs' command.
|
|
$END
|
|
|
|
#if defined (PUSHD_AND_POPD)
|
|
/* Some useful commands whose behaviour has been observed in Csh. */
|
|
|
|
/* The list of remembered directories. */
|
|
static char **pushd_directory_list = (char **)NULL;
|
|
|
|
/* Number of existing slots in this list. */
|
|
static int directory_list_size = 0;
|
|
|
|
/* Offset to the end of the list. */
|
|
static int directory_list_offset = 0;
|
|
|
|
pushd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
char *temp, *current_directory;
|
|
int j = directory_list_offset - 1;
|
|
char direction = '+';
|
|
|
|
/* If there is no argument list then switch current and
|
|
top of list. */
|
|
if (!list)
|
|
{
|
|
if (!directory_list_offset)
|
|
{
|
|
builtin_error ("No other directory");
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
current_directory = get_working_directory ("pushd");
|
|
if (!current_directory)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
temp = pushd_directory_list[j];
|
|
pushd_directory_list[j] = current_directory;
|
|
goto change_to_temp;
|
|
}
|
|
else
|
|
{
|
|
direction = *(list->word->word);
|
|
if (direction == '+' || direction == '-')
|
|
{
|
|
int num;
|
|
if (1 == sscanf (&(list->word->word)[1], "%d", &num))
|
|
{
|
|
if (direction == '-')
|
|
num = directory_list_offset - num;
|
|
|
|
if (num > directory_list_offset || num < 0)
|
|
{
|
|
if (!directory_list_offset)
|
|
builtin_error ("Directory stack empty");
|
|
else
|
|
builtin_error ("Stack contains only %d directories",
|
|
directory_list_offset + 1);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
else
|
|
{
|
|
/* Rotate the stack num times. Remember, the
|
|
current directory acts like it is part of the
|
|
stack. */
|
|
temp = get_working_directory ("pushd");
|
|
|
|
if (!num)
|
|
goto change_to_temp;
|
|
|
|
do
|
|
{
|
|
char *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);
|
|
|
|
temp = savestring (temp);
|
|
change_to_temp:
|
|
{
|
|
int tt = EXECUTION_FAILURE;
|
|
|
|
if (temp)
|
|
{
|
|
tt = cd_to_string (temp);
|
|
free (temp);
|
|
}
|
|
|
|
if ((tt == EXECUTION_SUCCESS))
|
|
dirs_builtin ((WORD_LIST *)NULL);
|
|
|
|
return (tt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
return (EXECUTION_FAILURE);
|
|
|
|
if (cd_builtin (list) == EXECUTION_SUCCESS)
|
|
{
|
|
if (directory_list_offset == directory_list_size)
|
|
{
|
|
pushd_directory_list = (char **)
|
|
xrealloc (pushd_directory_list,
|
|
(directory_list_size += 10) * sizeof (char *));
|
|
}
|
|
pushd_directory_list[directory_list_offset++] = current_directory;
|
|
|
|
dirs_builtin ((WORD_LIST *)NULL);
|
|
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
free (current_directory);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
#endif /* PUSHD_AND_POPD */
|
|
|
|
$BUILTIN dirs
|
|
$FUNCTION dirs_builtin
|
|
$DEPENDS_ON PUSHD_AND_POPD
|
|
$SHORT_DOC dirs [-l]
|
|
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.
|
|
|
|
The -l flag specifies that `dirs' should not print shorthand versions
|
|
of directories which are relative to your home directory. This means
|
|
that `~/bin' might be displayed as `/homes/bfox/bin'.
|
|
$END
|
|
|
|
#if defined (PUSHD_AND_POPD)
|
|
/* Print the current list of directories on the directory stack. */
|
|
dirs_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
int i, format, desired_index, index_flag;
|
|
char *temp, *w;
|
|
|
|
format = index_flag = 0;
|
|
desired_index = -1;
|
|
/* Maybe do long form or print specific dir stack entry? */
|
|
while (list)
|
|
{
|
|
if (strcmp (list->word->word, "-l") == 0)
|
|
{
|
|
format++;
|
|
list = list->next;
|
|
}
|
|
else if (*list->word->word == '+' && all_digits (list->word->word + 1))
|
|
{
|
|
w = list->word->word + 1;
|
|
index_flag = 1;
|
|
i = atoi (w);
|
|
/* dirs +0 prints the current working directory. */
|
|
if (i == 0)
|
|
desired_index = i;
|
|
else if (i == directory_list_offset)
|
|
{
|
|
desired_index = 0;
|
|
index_flag = 2;
|
|
}
|
|
else
|
|
desired_index = directory_list_offset - i;
|
|
list = list->next;
|
|
}
|
|
else if (*list->word->word == '-' && all_digits (list->word->word + 1))
|
|
{
|
|
w = list->word->word + 1;
|
|
i = atoi (w);
|
|
index_flag = 2;
|
|
/* dirs -X where X is directory_list_offset prints the current
|
|
working directory. */
|
|
if (i == directory_list_offset)
|
|
{
|
|
index_flag = 1;
|
|
desired_index = 0;
|
|
}
|
|
else
|
|
desired_index = i;
|
|
list = list->next;
|
|
}
|
|
else
|
|
{
|
|
bad_option (list->word->word);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (index_flag && (desired_index < 0 || desired_index > directory_list_offset))
|
|
{
|
|
if (directory_list_offset == 0)
|
|
builtin_error ("directory stack empty");
|
|
else
|
|
builtin_error ("%s: bad directory stack index", w);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* The first directory printed is always the current working directory. */
|
|
if (!index_flag || (index_flag == 1 && desired_index == 0))
|
|
{
|
|
temp = get_working_directory ("dirs");
|
|
if (!temp)
|
|
temp = savestring ("<no directory>");
|
|
printf ("%s", format ? temp : polite_directory_format (temp));
|
|
free (temp);
|
|
if (index_flag)
|
|
{
|
|
putchar ('\n');
|
|
return EXECUTION_SUCCESS;
|
|
}
|
|
}
|
|
|
|
#define DIRSTACK_ENTRY(i) \
|
|
format ? pushd_directory_list[i] \
|
|
: polite_directory_format (pushd_directory_list[i])
|
|
|
|
/* Now print the requested directory stack entries. */
|
|
if (index_flag)
|
|
printf ("%s", DIRSTACK_ENTRY (desired_index));
|
|
else
|
|
for (i = (directory_list_offset - 1); i > -1; i--)
|
|
printf (" %s", DIRSTACK_ENTRY (i));
|
|
|
|
putchar ('\n');
|
|
fflush (stdout);
|
|
return (EXECUTION_SUCCESS);
|
|
}
|
|
#endif /* PUSHD_AND_POPD */
|
|
|
|
$BUILTIN popd
|
|
$FUNCTION popd_builtin
|
|
$DEPENDS_ON PUSHD_AND_POPD
|
|
$SHORT_DOC popd [+n | -n]
|
|
Removes entries from the directory stack. With no arguments,
|
|
removes the top directory from the stack, and cd's to the new
|
|
top directory.
|
|
|
|
+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.
|
|
|
|
You can see the directory stack with the `dirs' command.
|
|
$END
|
|
|
|
#if defined (PUSHD_AND_POPD)
|
|
/* 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. */
|
|
popd_builtin (list)
|
|
WORD_LIST *list;
|
|
{
|
|
register int i;
|
|
int which = 0;
|
|
char direction = '+';
|
|
|
|
if (list)
|
|
{
|
|
direction = *(list->word->word);
|
|
|
|
if ((direction != '+' && direction != '-') ||
|
|
(1 != sscanf (&((list->word->word)[1]), "%d", &which)))
|
|
{
|
|
builtin_error ("bad arg `%s'", list->word->word);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (which > directory_list_offset || (!directory_list_offset && !which))
|
|
{
|
|
if (!directory_list_offset)
|
|
builtin_error ("Directory stack empty");
|
|
else
|
|
builtin_error ("Stack contains only %d directories",
|
|
directory_list_offset + 1);
|
|
return (EXECUTION_FAILURE);
|
|
}
|
|
|
|
/* Handle case of no specification, or top of stack specification. */
|
|
if ((direction == '+' && which == 0) ||
|
|
(direction == '-' && which == directory_list_offset))
|
|
{
|
|
i = cd_to_string (pushd_directory_list[directory_list_offset - 1]);
|
|
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. */
|
|
|
|
if (direction == '+')
|
|
i = directory_list_offset - which;
|
|
else
|
|
i = 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);
|
|
}
|
|
#endif /* PUSHD_AND_POPD */
|
|
|
|
/* Do the work of changing to the directory NEWDIR. Handle symbolic
|
|
link following, etc. */
|
|
|
|
static int
|
|
change_to_directory (newdir)
|
|
char *newdir;
|
|
{
|
|
char *t;
|
|
|
|
if (!no_symbolic_links)
|
|
{
|
|
int chdir_return = 0;
|
|
char *tdir = (char *)NULL;
|
|
|
|
if (!the_current_working_directory)
|
|
{
|
|
t = get_working_directory ("cd_links");
|
|
FREE (t);
|
|
}
|
|
|
|
if (the_current_working_directory)
|
|
t = make_absolute (newdir, the_current_working_directory);
|
|
else
|
|
t = savestring (newdir);
|
|
|
|
/* TDIR is the canonicalized absolute pathname of the NEWDIR. */
|
|
tdir = canonicalize_pathname (t);
|
|
|
|
/* Use the canonicalized version of NEWDIR, or, if canonicalization
|
|
failed, use the non-canonical form. */
|
|
if (tdir && *tdir)
|
|
free (t);
|
|
else
|
|
{
|
|
FREE (tdir);
|
|
|
|
tdir = t;
|
|
}
|
|
|
|
if (chdir (tdir) < 0)
|
|
{
|
|
int err;
|
|
|
|
chdir_return = 0;
|
|
free (tdir);
|
|
|
|
err = errno;
|
|
|
|
/* We failed changing to the canonicalized directory name. Try
|
|
what the user passed verbatim. If we succeed, reinitialize
|
|
the_current_working_directory. */
|
|
if (chdir (newdir) == 0)
|
|
{
|
|
chdir_return = 1;
|
|
if (the_current_working_directory)
|
|
{
|
|
free (the_current_working_directory);
|
|
the_current_working_directory = (char *)NULL;
|
|
}
|
|
|
|
tdir = get_working_directory ("cd");
|
|
FREE (tdir);
|
|
}
|
|
else
|
|
errno = err;
|
|
}
|
|
else
|
|
{
|
|
chdir_return = 1;
|
|
|
|
FREE (the_current_working_directory);
|
|
the_current_working_directory = tdir;
|
|
}
|
|
|
|
return (chdir_return);
|
|
}
|
|
else
|
|
{
|
|
if (chdir (newdir) < 0)
|
|
return (0);
|
|
else
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
/* 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 = make_word_list (make_word (name), NULL);
|
|
int result = (cd_builtin (tlist));
|
|
dispose_words (tlist);
|
|
return (result);
|
|
}
|