改变目录的 Bash 函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10621483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 02:17:19  来源:igfitidea点击:

Bash function that changes directory

bashcd

提问by Joshua Cheek

I have a common use case that I'd like to write a function for: I often want to cd to some directory relative to some file.

我有一个常见的用例,我想为其编写一个函数:我经常想 cd 到与某个文件相关的某个目录。

My current workflow looks like this:

我当前的工作流程如下所示:

$ gem which rspec/core | xargs echo -n | pbcopy
$ cd *paste and delete end until direcory looks right*

note: gem which rspec/coreprints something like "/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0/lib/rspec/core.rb"

注意:gem which rspec/core打印类似“/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0/lib/rspec/core.rb”的内容

I'd like it to look like this:

我希望它看起来像这样:

$ gem which rspec/core | 2dir 3

Which will cd me into "/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0" (passing the argument "3" tells it to remove "lib/rspec/core.rb" from the end)

这将使我进入“/Users/joshcheek/.rvm/gems/ruby-1.9.3-p125/gems/rspec-core-2.10.0”(传递参数“3”告诉它删除“lib/rspec/ core.rb”从最后)

This is the best I've gotten so far:

这是迄今为止我得到的最好的:

2dir() {
  read dir
  for i in $(seq 1 )
    do
      dir="${dir%/*}"
  done
  cd "$dir"
}

But the cd changes the function's directory, not mine.I've tried swapping it with an alias, but can't figure out how to make anonymous functions or pass the argument.

但是 cd 更改了函数的目录,而不是我的。我试过用别名交换它,但不知道如何制作匿名函数或传递参数。

回答by Jonathan Leffler

I'd use:

我会用:

2dir()
{
    name=${2:?'Usage: 2dir count path'}
    count=
    while [[ $count -gt 0 ]]; do name=$(dirname "$name"); ((count--)); done
    cd "$name"
}

and use it as:

并将其用作:

2dir 3 $(gem which rspec/core)

This works where your pipeline can't. The cdin the pipe process affects that (sub-)shell, but cannot affect the current directory of the parent process. This function can be made to work.

这适用于您的管道无法使用的地方。将cd在管的过程会影响(子)的外壳,但不能影响父进程的当前目录。这个功能可以工作。

And you can use your dir="${dir%/*}"in place of my dirnameif you prefer, except that you'll end up in your home directory instead of the current directory (or root directory, depending on whether you gave a relative or absolute path name) if you specify 10 when there are only 5 components.

如果您愿意,您可以使用您dir="${dir%/*}"的代替我的dirname,但如果您指定 10当只有 5 个组件时。

回答by Gordon Davisson

Here's a variant of @Jonathan Leffler's suggestion to streamline usage a little -- it makes the count argument optional, and avoids the need for $( )around the command:

这是@Jonathan Leffler 建议的一个变体,可以稍微简化使用——它使 count 参数成为可选,并避免$( )了命令周围的需要:

2dir() {
# If first arg is a number, use it as a trim count; otherwise assume 2
if [[ "" =~ ^[0-9]+$ ]]; then
    count=""
    shift
else
    count=2
fi

if [[ $# -lt 1 ]]; then  # Make sure a command was specified
    echo "Usage: 2dir [count] command [commandargs ...]" >&2
    return 1
fi

name="$("$@")"  # Execute the remaining args as a command to get the target directory
while [[ $count -gt 0 ]]; do name=$(dirname "$name"); ((count--)); done
cd "$name"
}

Example uses:

示例用途:

2dir 3 gem which rspec/core
2dir gem which rspec/core

回答by James Youngman

The command gem which rspec/core | 2dir 3is known as a "pipeline" in shell parlance. Each command in the pipeline is executed as a separate process. If one of the commands in the pipeline is a shell function, it may be executed by the current (interactive) shell process. But it is not guaranteed, and in your case this is not happening.

该命令 gem which rspec/core | 2dir 3在 shell 术语中称为“管道”。管道中的每个命令都作为一个单独的进程执行。如果管道中的命令之一是 shell 函数,则它可以由当前(交互式)shell 进程执行。但不能保证,在您的情况下,这不会发生。

To fix your problem you just need to make sure that the function is evaluated in the interactive shell. You just need to fix the function and then use it differently. Here is the updated function:

要解决您的问题,您只需要确保在交互式 shell 中评估该函数。您只需要修复该功能,然后以不同的方式使用它。这是更新的功能:

2dir() {
  declare -ir snip=""
  declare dir=""
  for i in $(seq 1 "$snip"); do
      dir="${dir%/*}"
  done
  cd "$dir"
}

You use it like this:

你像这样使用它:

$ 2dir 3 "$(gem which rspec/core)"

回答by bmargulies

A shell script can't change the working directory of the interactive shell. Only an alias can do that, since it runs in the shell whose directory you are trying to change.

shell 脚本不能更改交互式 shell 的工作目录。只有别名可以做到这一点,因为它在您试图更改其目录的 shell 中运行。

In other words:

换句话说:

There is a Linux process running the shell and accept commands from you. It has a working directory. When you tell it to execute a shell script, it creates a brand new process with an independent working directory, disconnected from the first.

有一个 Linux 进程运行 shell 并接受您的命令。它有一个工作目录。当您告诉它执行 shell 脚本时,它会创建一个具有独立工作目录的全新进程,与第一个进程断开连接。

回答by Paused until further notice.

Based on Jonathan Leffler's answer, but without a loop:

基于 Jonathan Leffler 的回答,但没有循环:

2dir () {
    local levels name=${2:?"Usage: $FUNCNAME count path"};
    printf -v levels '%*s' "" '';
    cd "/${name%${levels// //*}}"
}

One annoyance is that it produces perfectly validdirectories with leading double slashes(e.g. echo "$PWD"outputs "//foo/bar/baz" after using the function).

一个烦恼是它会生成完全有效的目录,并带有前导双斜杠(例如,echo "$PWD"在使用该函数后输出“//foo/bar/baz”)。

Another is that it's "too clever by half."

另一个是它“太聪明了一半”。

Edit:

编辑:

Fixed the double slash issue:

修复了双斜线问题:

2dir () {
    local levels name=${2:?"Usage: $FUNCNAME count path"};
    printf -v levels '%*s'  '';
    name=/${name%${levels// //*}};
    cd "${name/\/\///}"
}