mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-08 10:51:15 -03:00
@@ -6,7 +6,7 @@ Synopsis
|
||||
|
||||
.. synopsis::
|
||||
|
||||
cd [DIRECTORY]
|
||||
cd [( -L | --no-dereference ) | ( -P | --dereference )] [DIRECTORY]
|
||||
|
||||
Description
|
||||
-----------
|
||||
@@ -18,11 +18,22 @@ Description
|
||||
|
||||
``cd`` changes the current working directory.
|
||||
|
||||
The :envvar:`PWD` environment variable is updated with the new working directory, and the previous directory
|
||||
is added to the :ref:`directory history <directory-history>`.
|
||||
|
||||
If *DIRECTORY* is given, it will become the new directory. If no parameter is given, the :envvar:`HOME` environment variable will be used.
|
||||
|
||||
If *DIRECTORY* is a relative path, all the paths in the :envvar:`CDPATH` will be tried as prefixes for it, in addition to :envvar:`PWD`.
|
||||
It is recommended to keep **.** as the first element of :envvar:`CDPATH`, or :envvar:`PWD` will be tried last.
|
||||
|
||||
The new directory name is partially resolved to remove redundant segments (``.`` or ``..``).
|
||||
|
||||
``cd`` defaults to treating symbolic links as real directories, and not resolving them to their underlying
|
||||
targets. The ``$PWD`` :ref:`special variable <variables-special>` variable will contain the path that was
|
||||
supplied. This default behaviour can be enforced with the ``-L`` or ``--no-dereference`` option.
|
||||
|
||||
The ``-P`` or ``--dereference`` option resolves all symbolic links first. This was the default in fish versions before 3.0.0.
|
||||
|
||||
Fish will also try to change directory if given a command that looks like a directory (starting with **.**, **/** or **~**, or ending with **/**), without explicitly requiring **cd**.
|
||||
|
||||
Fish also ships a wrapper function around the builtin **cd** that understands ``cd -`` as changing to the previous directory.
|
||||
@@ -45,6 +56,9 @@ Examples
|
||||
cd /usr/src/fish-shell
|
||||
# changes the working directory to /usr/src/fish-shell
|
||||
|
||||
cd -P /tmp/link
|
||||
# resolves /tmp/link to its target before recording the directory
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
complete -c cd -a "(__fish_complete_cd)"
|
||||
complete -c cd -s h -l help -d 'Display help and exit'
|
||||
complete -c cd -s L -l no-dereference -d 'Change directory without resolving symbolic links'
|
||||
complete -c cd -s P -l dereference -d 'Resolve symbolic links before changing directory'
|
||||
|
||||
@@ -5,14 +5,6 @@
|
||||
function cd --description "Change directory"
|
||||
set -l MAX_DIR_HIST 25
|
||||
|
||||
if set -q argv[2]; and begin
|
||||
set -q argv[3]
|
||||
or not test "$argv[1]" = --
|
||||
end
|
||||
printf "%s\n" (_ "Too many args for cd command") >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
# Skip history in subshells.
|
||||
if status is-command-substitution
|
||||
builtin cd $argv
|
||||
|
||||
@@ -7,13 +7,20 @@
|
||||
fds::{BEST_O_SEARCH, wopen_dir},
|
||||
parser::ParserEnvSetMode,
|
||||
path::path_apply_cdpath,
|
||||
wutil::{normalize_path, wreadlink},
|
||||
wutil::{normalize_path, wreadlink, wrealpath},
|
||||
};
|
||||
use errno::Errno;
|
||||
use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
|
||||
use nix::unistd::fchdir;
|
||||
use std::sync::Arc;
|
||||
|
||||
const SHORT_OPTIONS: &wstr = L!("hLP");
|
||||
const LONG_OPTIONS: &[WOption] = &[
|
||||
wopt(L!("help"), ArgType::NoArgument, 'h'),
|
||||
wopt(L!("no-dereference"), ArgType::NoArgument, 'L'),
|
||||
wopt(L!("dereference"), ArgType::NoArgument, 'P'),
|
||||
];
|
||||
|
||||
// The cd builtin. Changes the current directory to the one specified or to $HOME if none is
|
||||
// specified. The directory can be relative to any directory in the CDPATH variable.
|
||||
pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
|
||||
@@ -26,18 +33,43 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
};
|
||||
|
||||
let opts = HelpOnlyCmdOpts::parse(args, parser, streams)?;
|
||||
let argc = args.len();
|
||||
let mut deref_symlink = false;
|
||||
let mut w = WGetopter::new(SHORT_OPTIONS, LONG_OPTIONS, args);
|
||||
while let Some(opt) = w.next_opt() {
|
||||
match opt {
|
||||
'L' => deref_symlink = false,
|
||||
'P' => deref_symlink = true,
|
||||
'h' => {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
';' => {
|
||||
builtin_unexpected_argument(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
'?' => {
|
||||
builtin_unknown_option(parser, streams, cmd, args[w.wopt_index - 1], false);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
_ => panic!("unexpected option {}", opt),
|
||||
}
|
||||
}
|
||||
|
||||
if opts.print_help {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return Ok(SUCCESS);
|
||||
let optind = w.wopt_index;
|
||||
let non_option_argc = argc - optind;
|
||||
if non_option_argc > 1 {
|
||||
err_fmt!(Error::UNEXP_ARG_COUNT, 1, non_option_argc)
|
||||
.cmd(cmd)
|
||||
.finish(streams);
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
|
||||
let vars = parser.vars();
|
||||
let tmpstr;
|
||||
|
||||
let dir_in: &wstr = if args.len() > opts.optind {
|
||||
args[opts.optind]
|
||||
let dir_in: &wstr = if non_option_argc == 1 {
|
||||
args[optind]
|
||||
} else {
|
||||
match vars.get_unless_empty(L!("HOME")) {
|
||||
Some(v) => {
|
||||
@@ -63,7 +95,15 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
}
|
||||
|
||||
let pwd = vars.get_pwd_slash();
|
||||
let mut pwd = vars.get_pwd_slash();
|
||||
if deref_symlink {
|
||||
if let Some(mut real_pwd) = wrealpath(&pwd) {
|
||||
if !real_pwd.ends_with('/') {
|
||||
real_pwd.push('/');
|
||||
}
|
||||
pwd = real_pwd;
|
||||
}
|
||||
}
|
||||
|
||||
let dirs = path_apply_cdpath(dir_in, &pwd, vars);
|
||||
assert!(
|
||||
@@ -120,10 +160,17 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
// Eventually fish will support distinct CWDs for different parsers in different threads.
|
||||
parser.libdata_mut().cwd_fd = Some(Arc::new(fd));
|
||||
|
||||
let mut new_pwd = norm_dir;
|
||||
if deref_symlink {
|
||||
if let Some(real_dir) = wrealpath(&new_pwd) {
|
||||
new_pwd = real_dir;
|
||||
}
|
||||
}
|
||||
|
||||
parser.set_var_and_fire(
|
||||
L!("PWD"),
|
||||
ParserEnvSetMode::new(EnvMode::EXPORT | EnvMode::GLOBAL),
|
||||
vec![norm_dir],
|
||||
vec![new_pwd],
|
||||
);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,20 @@ else
|
||||
end
|
||||
# CHECKERR: pwd: realpath failed: {{.+}}
|
||||
|
||||
mkdir $real/subdir
|
||||
cd $link
|
||||
test "$PWD" = "$link" || echo "Default cd should keep symlink:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
cd -P $link
|
||||
test "$PWD" = "$real" || echo "cd -P should resolve symlink:"\n "\$PWD: $PWD"\n "\$real: $real"\n
|
||||
cd $link/subdir
|
||||
test "$PWD" = "$link/subdir" || echo "Logical cd should keep subdir symlink:"\n "\$PWD: $PWD"\n "\$link/subdir: $link/subdir"\n
|
||||
cd -P ..
|
||||
test "$PWD" = "$real" || echo "cd -P .. should use physical parent:"\n "\$PWD: $PWD"\n "\$real: $real"\n
|
||||
cd $link/subdir
|
||||
cd -L ..
|
||||
test "$PWD" = "$link" || echo "cd -L .. should use logical parent:"\n "\$PWD: $PWD"\n "\$link: $link"\n
|
||||
cd $base
|
||||
|
||||
# Create a symlink and verify logical completion.
|
||||
# create directory $base/through/the/looking/glass
|
||||
# symlink $base/somewhere/rabbithole -> $base/through/the/looking/glass
|
||||
|
||||
Reference in New Issue
Block a user