4

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

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

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

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

我希望它看起来像这样:

$ gem which rspec/core | 2dir 3

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

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

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

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

4

5 回答 5

9

我会使用:

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

并将其用作:

2dir 3 $(gem which rspec/core)

这适用于您的管道无法做到的地方。管道中的cd进程会影响该(子)shell,但不会影响父进程的当前目录。可以使此功能起作用。

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

于 2012-05-16T15:18:47.317 回答
2

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

2dir() {
# If first arg is a number, use it as a trim count; otherwise assume 2
if [[ "$1" =~ ^[0-9]+$ ]]; then
    count="$1"
    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"
}

示例用途:

2dir 3 gem which rspec/core
2dir gem which rspec/core
于 2012-05-16T18:09:57.137 回答
1

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

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

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

你像这样使用它:

$ 2dir 3 "$(gem which rspec/core)"
于 2012-05-16T15:21:38.850 回答
0

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

换句话说:

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

于 2012-05-16T15:16:59.970 回答
0

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

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

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

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

编辑:

修复了双斜线问题:

2dir () {
    local levels name=${2:?"Usage: $FUNCNAME count path"};
    printf -v levels '%*s' $1 '';
    name=/${name%${levels// //*}};
    cd "${name/\/\///}"
}
于 2012-05-17T03:51:21.890 回答