使用PUSTD和POPD命令目录导航
1. 概述
当我们使用多个目录时,它们之间的切换是不可避免的。在本教程中,我们将了解pushd和popd 命令作为快速有效地为目录添加书签的方法。
2. 为什么我们需要pushd和popd?
在目录导航方面,cd 命令是一个事实上的标准,可以解决我们的大多数用例:
可以看到,我们可以做三种目录跳转:
- 使用绝对路径直接跳转到特定目录cd path
- 使用cd *..*跳转到上一级目录
- 使用cd跳转到上次访问的目录-
但是,随着我们需要跟踪的目录数量的增加,这种方法变得越来越低效:
- 这些路径中的大多数通常太长而无法键入
- 即使具有路径 自动完成功能,我们也应该记住确切的路径前缀
如果在时间戳t4之后,我们可以直接跳转到dir1而无需从其路径中输入一个字母怎么办?好吧,我们很幸运,因为pushd和popd命令的组合给了我们这样的灵活性。
3. 简单的导航
对于我们中的一些人来说,打破使用cd命令切换目录的习惯可能具有挑战性。因此,让我们一劳永逸地解决可能使我们远离探索这些新工具的潜在阻力。
3.1. pushd和popd
首先,让我们先使用pushd命令通过绝对路径或相对路径跳转到任意目录,就像cd命令一样:
$ pushd <directory-path>
而且,如果我们需要使用pushd命令访问我们访问的最后一个目录,那么我们可以使用不带任何参数的popd命令:
$ popd
此时,我们必须清楚地了解cd –和popd命令不会表现出相同的行为。前者将我们带到最后访问的目录,另一方面,后者将我们带到我们使用pushd命令使用的上一个目录。
3.2. 别名
当然,不可否认,别名让我们的生活变得轻松。使用cd命令,我们可以使用别名快速跨目录跳转:
$ cd [alias]
在这里,别名可以是任何有效的目录别名:
- . 对于当前工作目录
- .. 识别父目录
-
- 最后访问的目录
在内部,这些别名使用了一些特殊的目录变量:
- 当前工作目录的*$PWD*
- *${PWD%/*}*使用前缀参数替换 来识别父目录
- $OLDPWD最后访问的目录
好吧,问题是所有这些别名和路径变量都不是特定于cd命令的。因此,我们也可以将它们与pushd命令一起使用:
$ pushd [alias]
此外,在没有别名或目录路径的情况下,pushd使用默认目录。而且,这通常是由$HOME变量标识的用户的主目录。
有了这个,我们现在可以使用pushd命令来进行cd命令可能的各种目录跳转。而且,这将派上用场,帮助我们改掉每次都使用cd命令的习惯。
4. 目录栈生态系统
到目前为止,我们刚刚看到了pushd和popd命令的基本更改目录功能。现在,让我们对由dirs 、pushd和popd命令组成的整个目录堆栈生态系统采取整体方法。
4.1. 基本
pushd和popd命令都不仅仅是更改当前工作目录。在内部,它们管理堆栈数据结构 ,以方便目录之间的轻松切换。虽然pushd命令在堆栈顶部添加一个目录,但另一方面,popd命令从顶部删除一个项目。
好吧,我们不要忘记dirs命令,它在记录目录堆栈的内容方面起着重要作用。事实上,每次我们使用pushd或popd命令时,都会默认调用dirs命令:
$ dirs
接下来,让我们通过查看所有这些命令的实际作用来消化这些基本概念:
我们必须注意托管目录堆栈的一个微妙特征。而且,这涉及堆栈顶部的目录。在任何时候,如果我们使用dirs命令检查这个目录堆栈的内容,那么我们总是会在顶部找到当前目录。
4.2. 枚举
堆栈数据结构的大多数常见实现通常仅通过推送或弹出功能公开堆栈的最顶部元素。但是,目录堆栈还使我们能够通过基于方向的相对索引访问堆栈中的所有目录。
如果我们以栈顶作为参考进行计数,则使用正索引。另一方面,当使用堆栈底部作为我们的参考时使用负索引:
而且,正如我们所见,有两种可用的索引变体:
- +N或*-N索引只能由目录堆栈生态系统内部的命令使用 - 即dirs*、pushd和popd
- ~+N或*~-N索引甚至可以被cd*、ls和chmod等接受目录路径作为参数的外部命令使用
虽然我们可以使用带有*-v选项的dirs*命令进行索引查找:
$ dirs -v
另一方面,我们可以使用*+N或-N*索引对目录内容中特定索引处的目录进行反向查找:
$ dirs [+N|-N]
4.3. 值传递与引用传递
查看这两种枚举的另一种微妙但重要的方法是查看目录参数是作为值传递还是作为引用传递。虽然~+N / ~-N枚举使用按值传递 策略,但另一方面,+N/-N枚举使用按引用传递 策略**。
直观地说,诸如cd 和ls等命令不知道目录堆栈的内部结构和实现。因此,所有外部命令都不能改变目录堆栈是有道理的,因为它们受到按值传递策略的限制。
另一方面,诸如dirs、pushd和popd之类的命令非常了解目录堆栈的内部结构。因此,他们可以使用传递引用策略来改变堆栈的特定位置。
随着我们的深入,我们将解决一些高级用例,这些用例将利用这些策略来引用堆栈中的特定目录。
5. 目录书签
对于我们大多数人来说,在个人工作和专业工作之间切换是很常见的。在本节中,我们将学习如何以有效的方式使用一组目录初始化目录堆栈。
5.1.目录结构
假设我们的主目录下有单独的目录来存储我们的个人和工作相关数据:
~
|-- personal
| |-- family
| |-- movies
| `-- pictures
`-- work
|-- proj1
|-- proj2
`-- proj3
我们将使用这个目录结构来解决一些与目录导航和目录堆栈管理相关的高级用例。
5.2. -n开关
假设我们只想专注于工作。因此,在堆栈中拥有所有相关的*~/work/project*目录可能会提高我们的生产力。
默认情况下,我们的目录堆栈的顶部预先填充了默认目录,在我们的例子中是主目录:
$ dirs -p -v
0 ~
这是完美的,因为我们不打算更改我们当前的工作目录。
虽然我们可以在这里轻松地使用pushd命令,但默认情况下,pushd和popd命令都会更改当前工作目录并改变堆栈顶部。而且,这不是我们目前想要的。事实上,我们需要的是当我们改变堆栈的其余部分时,堆栈的顶部应该保持原样。
至此,我们知道栈顶总是与当前工作目录相关联的不变量。因此,为了实现栈顶的不变性,我们需要一种方法来禁用pushd命令的目录更改功能。而且,这就是*-n*开关可以帮助我们的地方:
接下来,让我们使用它来用我们的工作特定目录填充我们的目录堆栈:
$ for dir in $(echo "~/work/proj"{1,2,3}); do
> pushd -n $dir 1>/dev/null
> done
最后,让我们检查一下目录堆栈:
$ dirs -p -v
0 ~
1 ~/work/proj3
2 ~/work/proj2
3 ~/work/proj1
看起来不错,只是我们刚刚意识到proj1已经完成。不用担心,让我们使用它的参考来弹出它:
$ popd +3 1>/dev/null
$ dirs -p -v
0 ~
1 ~/work/proj3
2 ~/work/proj2
6. 书签清理
随着时间的推移,我们可能会无意中将一些与我们当前关注的领域无关的目录推送到堆栈中。因此,让我们学习如何在 Bash 脚本中使用pushd和popd来自动化从堆栈中清除所有上下文之外的目录书签的过程。
6.1. 自动化
首先,让我们看一个需要清理的目录堆栈示例:
$ dirs -v -p
0 ~/work/proj1
1 ~/personal/pictures
2 ~/work/proj2
3 ~/personal/family
4 ~
现在,让我们计划一个清理策略:
- 我们可以弹出所有不属于*~/current_focus*目录的目录
- 如果所有目录都脱离上下文,则将*~/current_focus*目录放在堆栈顶部
好吧,我们明智的做法是自动执行此清理任务。但是,为此,我们需要了解dirs、pushd和popd是内置的 shell 函数,并且每个运行的 shell 实例都维护自己的目录堆栈副本。
由于我们的目标是清理当前交互式 Bash 会话的目录堆栈,因此我们需要将代码放在*~/.bashrc*文件中。这是因为每个交互式 Bash 会话都会调用此脚本并将所有函数加载到当前 bash 进程中。
6.2. 脚本在行动
让我们首先编写一个名为*clean_dir_bookmarks()*的 bash 函数,它将第一个参数 ( $1 ) 视为所需的焦点区域:
function clean_dir_bookmarks() {
focus="$1"
focus_dir=$HOME/$focus
typeset -i index=1
}
接下来,让我们在同一个函数中遍历目录堆栈。而且,如果我们遇到不属于当前焦点区域的目录,那么我们将在不更改当前目录的情况下将其弹出:
while true
do
stack_index="$(printf "+%d" $index)"
dir="$(dirs -l $stack_index 2>/dev/null)"
if [ $? -eq 0 ]
then
if [[ ! "$dir" =~ "$focus_dir" ]]
then
popd -n $stack_index 1>/dev/null
fi
else
break
fi
index=index+1
done
我们必须注意到,我们依赖dirs 命令的成功执行来保持循环。这是可能的,因为dirs命令在用于检查堆栈中不存在的位置时保证非零退出代码。
到目前为止,我们还没有处理栈顶,因为我们需要对其进行不同的处理。当顶层目录属于当前焦点区域时,不需要任何操作。但是,如果它脱离上下文,那么我们将其替换为下一个可用目录。而且,如果这不可用,那么我们只需将*~/focus*目录保留在顶部。
好吧,让我们继续完成我们的*clean_dir_bookmarks()*函数:
top_dir="$(dirs -l +0 2>/dev/null)"
if [[ ! "$top_dir" =~ "$focus_dir" ]]
then
dirs +1 1>/dev/null 2>/dev/null
if [ $? -eq 0 ]
then
popd 1>/dev/null
else
pushd $focus_dir 1>/dev/null
popd +1 1>/dev/null
fi
fi
最后,让我们使用我们的函数进行所需的清理:
$ clean_dir_bookmarks personal
$ dirs -v -p
0 ~/personal/pictures
1 ~/personal/family
7. 堆栈旋转
我们并不总是需要一个显式循环来处理堆栈中的多个目录。在本节中,我们将了解与目录堆栈轮换相关的高级概念。
7.1.不完整的旋转周期
假设我们正在做一些与工作相关的项目,然后我们决定休息一下。此外,在休息期间,我们开始浏览一些家庭照片。结果,我们与工作相关的目录现在被推到堆栈的底部:
$ dirs -v -p
0 ~/personal/family
1 ~/personal/pictures
2 ~/work/proj3
3 ~/work/proj1
现在,我们想将注意力转移到工作上,但我们可能想在短时间内重新访问个人目录。自然,在会弹出个人目录的情况下,使用*popd不是一个好的选择。*但是,如果我们查看堆栈的内容,我们可以看到我们的工作目录位于 [ +2 , +3 ] 堆栈索引之间。而且,我们真正需要的是将所有这些目录移到顶部。
嗯,这是在目录之间切换的一个非常常见的用例。而且,好消息是pushd命令通过提供堆栈轮换功能解决了这个问题。每当我们将堆栈索引与pushd一起使用时,它都会相对于索引执行不完整的循环循环,并且堆栈顶部会更改为该索引引用的目录:
因此,我们可以对这项工作使用正索引或负索引。所以,让我们继续使用正索引来旋转我们的目录堆栈:
$ pushd +2
$ dirs -v -p
0 ~/work/proj3
1 ~/work/proj1
2 ~/personal/family
3 ~/personal/pictures
瞧!我们的目录堆栈正是我们想要的。
7.2. 使用完整的旋转周期进行更换
通过对*+i位置的一次pushd调用,我们可以获得包含Ni顺时针旋转的不完整旋转循环。现在,如果我们再次调用pushd* ,但对于*+(Ni)位置,那么我们将得到另一个具有N-(Ni)=i*顺时针旋转的不完整循环:
$ pushd +i
$ pushd +(N-i)
而且,如果我们把它们加起来,那么我们正好有一个完整的旋转周期。这实质上意味着堆栈中任何目录项的位置都没有净变化。
现在,假设我们要将所有特定于工作的项目目录保留在堆栈中。此外,如果我们检查目录堆栈,我们会看到堆栈中的所有此类目录都可用,除了*~/work/proj3*目录:
$ dirs -v -p
0 ~/work/proj1
1 ~/work
2 ~/personal/family
3 ~/work/proj2
此外,我们注意到*~/personal/family占据了我们想要放置proj3*目录的位置。
到目前为止,我们知道我们可以使用popd的位置参数 从堆栈中删除特定目录。但是,到目前为止,我们还没有学会一种复杂的替换策略,可以帮助我们替换堆栈中的特定位置而不会干扰其他目录。尽管如此,如果我们应用我们对整个轮换周期的了解,那么替换特定目录并不是那么困难。
首先,让我们做一个不完整的轮换循环,将*~/personal/family*目录带到栈顶:
$ pushd +2
$ dirs -v -p
0 ~/personal/family
1 ~/work/proj2
2 ~/work/proj1
3 ~/work
接下来,让我们通过依次执行popd和pushd命令,用~/work/proj3*替换栈顶:*
$ popd
$ pushd ~/work/proj3
$ dirs -v -p
0 ~/work/proj3
1 ~/work/proj2
2 ~/work/proj1
3 ~/work
最后,让我们完成轮换循环,以便*~/work/proj3*进入 +2 索引,而不会对其他目录进行任何净更改:
$ pushd +2
$ dirs -v -p
0 ~/work/proj1
1 ~/work
2 ~/work/proj3
3 ~/work/proj2
看起来我们的努力取得了成功。
7.3. 使用克隆和轮换替换
现在,假设我们在目录堆栈中有所有特定于工作的项目目录:
$ dirs -v -p
0 ~/work
1 ~/work/proj1
2 ~/work/proj3
而且,假设我们决定通过将*~/work/proj1目录移动到~/work/archived/proj1*来归档它。不幸的是,在这种情况下,目录堆栈中的现有目录不会重命名自己。看来我们得自己动手了。
此外,我们不能通过使用完整轮换周期的简单替换策略来做到这一点。这是因为中间的popd命令会尝试将目录更改为不存在的目录,并以失败告终。
这就是我们可以利用基于克隆的轮换周期的地方:
$ pushd -n +i
这个想法是,通过使用-n*开关,我们可以保留堆栈的顶部*。而且,这种操作的结果是修改了一个简单的循环循环,其中目录的顶部用它的 clone替换了 +( Ni ) 定位的目录:
所以,这一次,让我们首先将目标目录压入堆栈:
$ pushd ~/work/archived/proj1
$ dirs -v -p
0 ~/work/archived/proj1
1 ~/work
2 ~/work/proj1
3 ~/work/proj3
接下来,让我们使用克隆技术进行全周期轮换:
$ pushd -n +2
$ pushd -n +2
$ dirs -v -p
0 ~/work/archived/proj1
1 ~/work
2 ~/work/archived/proj1
3 ~/work/proj3
最后,让我们从堆栈顶部弹出重复项:
$ popd
$ dirs -v -p
0 ~/work
1 ~/work/archived/proj1
2 ~/work/proj3
耶!我们做到了。