mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-19 19:15:40 +08:00
builtin realpath: Add --no-symlinks option
Taken from GNU realpath, this one makes realpath not resolve symlinks. It still makes paths absolute and handles duplicate and trailing slashes. (useful in fish_add_path)
This commit is contained in:
parent
8cf389baf2
commit
568f9031aa
|
@ -15,6 +15,10 @@ Description
|
|||
|
||||
``realpath`` resolves a path to its absolute path.
|
||||
|
||||
fish provides a ``realpath`` builtin as a fallback for systems where there is no ``realpath`` command. fish's implementation always resolves its first argument, and does not support any options.
|
||||
fish provides a ``realpath`` builtin as a fallback for systems where there is no ``realpath`` command, your OS might provide a version with more features.
|
||||
|
||||
If the operation fails, an error will be reported.
|
||||
If a ``realpath`` command exists, it will be preferred, so if you want to use the builtin you should use ``builtin realpath`` explicitly.
|
||||
|
||||
The following options are available:
|
||||
|
||||
- ``-s`` or ``--no-symlink``: Don't resolve symlinks, only make paths absolute, squash multiple slashes and remove trailing slashes.
|
||||
|
|
|
@ -12,18 +12,61 @@
|
|||
#include "common.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "io.h"
|
||||
#include "wgetopt.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
struct realpath_cmd_opts_t {
|
||||
bool print_help = false;
|
||||
bool no_symlinks = false;
|
||||
};
|
||||
|
||||
static const wchar_t *const short_options = L"+:hs";
|
||||
static const struct woption long_options[] = {{L"no-symlinks", no_argument, nullptr, 's'},
|
||||
{L"help", no_argument, nullptr, 'h'},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
static int parse_cmd_opts(realpath_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method)
|
||||
int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
||||
switch (opt) {
|
||||
case 's': {
|
||||
opts.no_symlinks = true;
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
opts.print_help = true;
|
||||
break;
|
||||
}
|
||||
case ':': {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case '?': {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*optind = w.woptind;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
/// An implementation of the external realpath command. Doesn't support any options.
|
||||
/// In general scripts shouldn't invoke this directly. They should just use `realpath` which
|
||||
/// will fallback to this builtin if an external command cannot be found.
|
||||
maybe_t<int> builtin_realpath(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
help_only_cmd_opts_t opts;
|
||||
realpath_cmd_opts_t opts;
|
||||
int argc = builtin_count_args(argv);
|
||||
int optind;
|
||||
int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
||||
|
||||
int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
if (opts.print_help) {
|
||||
|
@ -37,20 +80,24 @@ maybe_t<int> builtin_realpath(parser_t &parser, io_streams_t &streams, wchar_t *
|
|||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
|
||||
if (auto real_path = wrealpath(argv[optind])) {
|
||||
streams.out.append(*real_path);
|
||||
} else {
|
||||
if (errno) {
|
||||
// realpath() just couldn't do it. Report the error and make it clear
|
||||
// this is an error from our builtin, not the system's realpath.
|
||||
streams.err.append_format(L"builtin %ls: %ls: %s\n", cmd, argv[optind],
|
||||
std::strerror(errno));
|
||||
if (!opts.no_symlinks) {
|
||||
if (auto real_path = wrealpath(argv[optind])) {
|
||||
streams.out.append(*real_path);
|
||||
} else {
|
||||
// Who knows. Probably a bug in our wrealpath() implementation.
|
||||
streams.err.append_format(_(L"builtin %ls: Invalid path: %ls\n"), cmd, argv[optind]);
|
||||
}
|
||||
if (errno) {
|
||||
// realpath() just couldn't do it. Report the error and make it clear
|
||||
// this is an error from our builtin, not the system's realpath.
|
||||
streams.err.append_format(L"builtin %ls: %ls: %s\n", cmd, argv[optind],
|
||||
std::strerror(errno));
|
||||
} else {
|
||||
// Who knows. Probably a bug in our wrealpath() implementation.
|
||||
streams.err.append_format(_(L"builtin %ls: Invalid path: %ls\n"), cmd, argv[optind]);
|
||||
}
|
||||
|
||||
return STATUS_CMD_ERROR;
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
} else {
|
||||
streams.out.append(normalize_path(argv[optind], /* allow leading double slashes */ false));
|
||||
}
|
||||
|
||||
streams.out.append(L"\n");
|
||||
|
|
|
@ -403,7 +403,7 @@ maybe_t<wcstring> wrealpath(const wcstring &pathname) {
|
|||
return str2wcstring(real_path);
|
||||
}
|
||||
|
||||
wcstring normalize_path(const wcstring &path) {
|
||||
wcstring normalize_path(const wcstring &path, bool allow_leading_double_slashes) {
|
||||
// Count the leading slashes.
|
||||
const wchar_t sep = L'/';
|
||||
size_t leading_slashes = 0;
|
||||
|
@ -430,7 +430,7 @@ wcstring normalize_path(const wcstring &path) {
|
|||
wcstring result = join_strings(new_comps, sep);
|
||||
// Prepend one or two leading slashes.
|
||||
// Two slashes are preserved. Three+ slashes are collapsed to one. (!)
|
||||
result.insert(0, leading_slashes > 2 ? 1 : leading_slashes, sep);
|
||||
result.insert(0, allow_leading_double_slashes && leading_slashes > 2 ? 1 : leading_slashes, sep);
|
||||
// Ensure ./ normalizes to . and not empty.
|
||||
if (result.empty()) result.push_back(L'.');
|
||||
return result;
|
||||
|
|
|
@ -76,7 +76,7 @@ maybe_t<wcstring> wrealpath(const wcstring &pathname);
|
|||
/// 1. Collapse multiple /s into a single /, except maybe at the beginning.
|
||||
/// 2. .. goes up a level.
|
||||
/// 3. Remove /./ in the middle.
|
||||
wcstring normalize_path(const wcstring &path);
|
||||
wcstring normalize_path(const wcstring &path, bool allow_leading_double_slashes = true);
|
||||
|
||||
/// Given an input path \p path and a working directory \p wd, do a "normalizing join" in a way
|
||||
/// appropriate for cd. That is, return effectively wd + path while resolving leading ../s from
|
||||
|
|
|
@ -62,6 +62,19 @@ else
|
|||
echo "fish-symlink not handled correctly: $real_path != $expected_real_path" >&2
|
||||
end
|
||||
|
||||
# With "-s" the symlink is not resolved.
|
||||
set -l real_path (builtin realpath -s $XDG_DATA_HOME/fish-symlink)
|
||||
set -l expected_real_path "$XDG_DATA_HOME/fish-symlink"
|
||||
if test "$real_path" = "$expected_real_path"
|
||||
echo "fish-symlink handled correctly"
|
||||
# CHECK: fish-symlink handled correctly
|
||||
else
|
||||
echo "fish-symlink not handled correctly: $real_path != $expected_real_path" >&2
|
||||
end
|
||||
|
||||
test (builtin realpath -s /usr/bin/../) = "/usr"
|
||||
or echo builtin realpath -s does not resolve .. or resolves symlink wrong
|
||||
|
||||
# A nonexistent file relative to a valid symlink to a directory gets converted.
|
||||
# This depends on the symlink created by the previous test.
|
||||
set -l real_path (builtin realpath $XDG_DATA_HOME/fish-symlink/nonexistent-file-relative-to-a-symlink)
|
||||
|
|
Loading…
Reference in New Issue
Block a user