Manipulating Directories - Shell Builtins cd pushd and popd
This page's content used LLM translation.
Most modern shells have a set of shell builtins (e.g. zsh), some of which can be used to manipulate directories.
To manipulate directories, a directory stack structure is maintained in memory. In bash it can also be accessed through the $DIRSTACK shell variable. bash’s introduction to the directory stack, zsh’s introduction to the directory stack
With pushd and popd, you can enter a series of directories and leave them in reverse order.
[1:cuso4d@nightcord-laborari:~]
$ pwd
/home/cuso4d
[1:cuso4d@nightcord-laborari:~]
$ pushd ~/source/zsh
~/source/zsh ~
[1:cuso4d@nightcord-laborari:~/source/zsh on master]
$ pushd ~/.ssh
~/.ssh ~/source/zsh ~
[1:cuso4d@nightcord-laborari:~/.ssh]
$ popd
~/source/zsh ~
[1:cuso4d@nightcord-laborari:~/source/zsh on master]
$ popd
~
[1:cuso4d@nightcord-laborari:~]
$ pwd
/home/cuso4d
Taking zsh as an example, the directory stack is implemented as a linked list. It is located in Src/Bulitin.c.
static struct builtin builtins[] =
{
// ...
BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
// ...
}
The second parameter specifies the behavior of
cd,popd, andpushd: treat invalid options as arguments; support-as an argument; support using--to indicate that everything after it is not an option.
/* Builtin option handling */
#define BINF_SKIPINVALID (1<<12) /* Treat invalid option as argument */
#define BINF_KEEPNUM (1<<13) /* `[-+]NUM' can be an option */
#define BINF_SKIPDASH (1<<14) /* Treat `-' as argument (maybe `+') */
#define BINF_DASHDASHVALID (1<<15) /* Handle `--' even if SKIPINVALD */
The third parameter specifies that all three builtins are implemented with a single
bin_cdfunction.The fourth and fifth parameters indicate the minimum and maximum number of arguments for this builtin
The sixth parameter is used when a single function is shared by multiple builtins, using different values to indicate which command invoked it, resulting in different behavior.
The seventh parameter is the list of available options, and the eighth is the options that must be used.
/* set if we are resolving links to their true paths */
static int chasinglinks;
/* The main pwd changing function. The real work is done by other *
* functions. cd_get_dest() does the initial argument processing; *
* cd_do_chdir() actually changes directory, if possible; cd_new_pwd() *
* does the ancillary processing associated with actually changing *
* directory. */
/**/
int
bin_cd(char *nam, char **argv, Options ops, int func)
{
LinkNode dir;
if (isset(RESTRICTED)) {
zwarnnam(nam, "restricted");
return 1;
}
doprintdir = (doprintdir == -1);
chasinglinks = OPT_ISSET(ops,'P') ||
(isset(CHASELINKS) && !OPT_ISSET(ops,'L'));
queue_signals();
zpushnode(dirstack, ztrdup(pwd));
if (!(dir = cd_get_dest(nam, argv, OPT_ISSET(ops,'s'), func))) {
zsfree(getlinknode(dirstack));
unqueue_signals();
return 1;
}
cd_new_pwd(func, dir, OPT_ISSET(ops, 'q'));
unqueue_signals();
return 0;
}
Declare a linked-list node
dirIf it is a restricted shell, it cannot be executed
If
doprintdiris -1, change it to 1; otherwise change it to 0 (is this some global variable that can turn itself back?.. no time to look for now)chasinglinksdetermines whether to resolve symbolic links. If the argument includes-P, or (chaselinksis set via setopt and there is no-Largument), resolve symbolic links; otherwise do not.queue_signals()is probably to ensure something like signal transactions…?Push the current directory onto the top of the stack
cd_get_dest()computes the target directory depending on which command was invoked (cd,pushd,popd); it returns non-zero if the target directory is absent or inaccessible (which can happen with the-sargument).cd_new_pwd()jumps to the directory it computed.