如何使用 Z-shell 编辑器、Regex 和钩子

介绍

zsh,也被称为Z壳,由Paul Falstad于1990年创建,为Bourne壳提供替代品。zsh提供了许多在其他壳中不可用的定制选项,使Z壳成为shbash的功能丰富的替代品,以及更少使用的壳,如Korn壳(ksh)和C壳(csh)。

本文将涵盖由zsh提供的功能的样本,包括使用zsh模拟其他壳,使用zsh内置功能编辑命令行,并使用外部工具vidirvipe,使用zsh命令中的常规表达式,并设置夹克来自动化壳行为。

前提条件

在开始使用本文中的代码之前,请确保您在系统上安装了终端模拟器,而您的zsh – 版本5.8.1或更高版本。

如果你正在使用Linux,你的zsh版本低于5.8.1,你可以使用系统包管理器进行更新,例如,在Ubuntu上使用apt将安装zsh版本5.9

macOS 用户需要安装一个更新版本的zsh,这可以通过homebrew工具完成:

1brew update && brew install zsh

要使用vidirvipe,您必须在您的计算机上安装moreutils包. 此包可用于 Linux 通过 joeyh.name/code/moreutils/,或与您的 Debian、Ubuntu 和 Arch 上的包管理器一起安装。

最后,本文假设您熟悉命令行工具,以及在终端中编辑文件。

现在,让我们开始吧!

与其他壳相容

虽然zsh是几种流行的命令行壳之一,但zsh的基本用途与其他壳不太不同,此外,zsh还包括与其他壳的某些兼容性选项,因为你喜欢其他壳提供的特定选项,或者你第一次测试zsh

zsh提供的工具来模仿其他壳的行为分为两类:

  1. 使用壳选项(setopt)
  2. 使用 emulate Builtin

让我们来看看你可以使用这些命令,从setopt开始。

1、使用setopt的 Shell 选项

setopt工具为您定制壳提供了许多选项,但对于本文的目的,我们只将涵盖最有用的选项,让您的zsh体验在来自另一个环境时变得更舒适。

但是首先,你怎么知道你的壳中有哪些选项是活跃的呢?在没有论点的情况下运行setopt会显示你已启用的选项列表,虽然这很有帮助,但你可能需要做一些挖掘来找到zsh壳中可用的选项的完整列表(https://zsh.sourceforge.io/Doc/Release/Options.html)。

ksh_option_印刷

ksh_option_print 选项允许您更改用 setopt 打印的选项列表,从显示仅有启用选项到显示每个选项和相应的打开关闭列。

1# setopt kshoptionprint will also work
2setopt ksh_option_print

以下是从setopt的选项的简化列表,每个选项都有启用/关闭指标。

1setopt
 1noaliases off
 2allexport on
 3noalwayslastprompt off
 4alwaystoend on
 5appendcreate off
 6noappendhistory off
 7autocd on
 8autocontinue off
 9noautolist off
10noautomenu off
11autonamedirs off
12noautoparamkeys off
13noautoparamslash off
14autopushd on
15noautoremoveslash off
16autoresume off
17nobgnice off
18braceccl off
19bsdecho on

bsd_echo 对话

默认情况下,zsh 会将 newline (\n) 或 tab (\t') 字符打印为非可见,但可能有时您需要在字符串中具有字面 backslash escaped 字符。

1setopt bsd_echo
2# prints tab and newline characters
3echo "\t\n"
4
5unsetopt bsd_echo
6
7# will print the literal "\t\n"
8echo "\t\n"

csh_junkie_路由器

csh_junkie_loops 选项为在列表或数组上循环提供了更为简单的语法。启用此选项允许您在循环中的每个项目上执行命令之前使用循环,而无需使用Do 关键字。

虽然这可能看起来不像是一个巨大的改进,但缩短的语法可以帮助您随着时间的推移更快地与壳进行交互,通过消除记住创建zsh中的语法正确循环所需的关键字的负担。

下面的例子:

1# the for command requires "do" before any actions
2# can be performed on the list of items
3# a traditional zsh loop
4for f in 1 3 4 5; do print $f; done

使用csh_junkie_loops选项启用,您可以使用更短的语法来实现相同的结果:

1setopt csh_junkie_loops
2for f in 1 3 4 5; echo $f; end
3$ 1
43
54
65

csh_nullcmd 可用

在清除终端中的文件内容时,您可以使用类似>file.txt的命令,该命令将重定向一个空串,以取代文件的内容. 如果您正在处理敏感数据,或者您想避免用空数据重写文件,您可以激活csh_nullcmd选项,以防止没有先前的命令发生文件重定向。

激活此选项后,不遵循命令的任何重定向都会导致错误。

1setopt csh_nullcmd
2
3# attempting to overwrite file.txt will create an error
4>file.txt
1zsh: redirection with no command

您可以通过在终端中输入unsetopt chs_nullcmd来禁用此选项。

zsh中,数组是一个索引的,这意味着您必须使用$array[1]来获取第一个项目,这种行为对那些习惯于使用包含零索引数组的编程语言的开发者来说是反直观的。

ksh中的数组允许您使用零索引,模仿许多编程语言的行为. 此选项还要求您使用弯曲的轴承来访问数组元素,而不是使用平方轴承,就像默认的zsh行为一样,弯曲的轴承是可选的。

1declare -a kitchen_items=(plates cutlery oven sink)
2print ${kitchen_items[1]}
3# plates

使用 ksh_arrays 选项集, $kitchen_items[1] 引用cutlery而不是plates

1setopt ksh_arrays
2declare -a kitchen_items=(plates cutlery oven sink)
3
4print ${kitchen_items[1]}
5# cutlery

sh_word_split 解析

默认情况下,zsh 不会将多单词变量分成单个元素,如shksh壳,这意味着包含三个单词的变量将被视为单个元素,而不是三个单独单词。

1kitchen_items="plates cutlery oven sink"
2
3# without sh_word_split
4for word in $kitchen_items; do
5    print "$word"
6end

要强迫zsh通过在间隙中划分模仿sh的行为,请使用setopt sh_word_split

1setopt sh_word_split

现在,让我们用略有不同的语法打印$kitchen_items的内容:

1kitchen_items="plates cutlery oven sink"
2for word in $kitchen_items[@]; do
3    print "$word"
4end
5# the loop above will print the following text:
6# plates
7# cutlery
8# oven
9# sink

上面的示例可以在您的壳中使用,或者为了更永久地使用setopt,您可以将上述任何setopt命令添加到您的.zshrc文件中,以便在任何交互式壳会话中提供这些选项。

但是,如果你发现自己需要一种不同的方式来模仿其他壳的行为,让我们看看模拟命令。

2、使用模拟命令

要模拟在zsh中另一个壳,您可以使用模拟 builtin 命令. 例如,要模拟 Bash 壳,您可以使用以下命令:

1emulate bash

这将使zsh在大多数方面表现得像Bash壳一样,然后您可以在zsh中运行Bash脚本和命令,并且它们应按预期工作。

要模拟其他壳,例如Bourne或Korn壳,您可以在模拟命令中使用相应的名称而不是bash,例如,要模拟Korn壳,您可以使用以下命令:

1emulate ksh

如果你有一个命令,你想在sh模拟模式中使用,你可以使用

1emulate sh

一旦设置,您可以通过在终端中键入模拟来打印当前模拟模式。

模拟命令还允许您使用c选项将粘贴模拟应用于函数,这意味着模拟命令将在该函数后执行的任何命令的范围内,在执行模拟命令后。

1function hello_world_sh () {
2    emulate sh -c  "print hello world";
3}

模拟sh -c命令可用于创建需要POSIX兼容的函数,同时仍然利用zsh环境。

在飞机上编辑

在日常工作中,您可能需要编辑许多类型的文件 - 您正在构建的应用程序的配置,或者如果您只是从任务列表中检查一个项目。

有几个工具可用于zsh,这将允许您无缝地编辑zsh内部环境的各个方面,从交互式终端的核心,zsh Line Editor开始。

zsh 线程编辑器

zsh 行编辑器,或 ZLE 是您与zsh一起使用的编辑器,用于输入和修改终端中的命令行上的命令.所有壳都有类似的行编辑器,在大多数情况下您不需要修改其行为。

ZLE在加载zsh时会自动激活,但它也存在于一个名为zsh/ZLEzsh 模块

zsh中,ZLE(zsh Line Editor)是内置的行编辑接口,允许您编辑命令行并导航命令历史。

如果您在交互式壳中使用zsh,则默认情况下启用了ZLE,因为它是zsh交互式功能的核心。

1# This command will print "ZLE: on" if the zsh Line Editor is enabled.
2[[ -o zle ]] && echo "zle: on"

直接与 ZLE 交互的主要用途是通过为您的交互性壳环境设置默认的关键链接,这些选项可以允许您使用特定于您喜爱的编辑器的关键链接,无论是emacs还是vi

如果您更喜欢 emacs 密钥绑定,请在终端中输入以下命令。

1bindkey -e

要启用 vi keybindings,请使用以下命令:

1bindkey -v

现在你已经了解了一点关于 zsh 行编辑器,让我们看看你如何使用它与zed编辑器。

西德

「Zed」使用ZLE,允许您编辑命令行上的脚本和功能,而无需打开编辑器。这对于您导航文件系统时快速编辑非常好,但也可以用作任何一般编辑命令的基本编辑器。

要开始,在您的终端中键入zed,以检查它已安装在您的系统上。

1zed

如果zed命令不可用,则可以从 https://github.com/zsh-users/zsh/blob/master/Functions/Misc/zed获取代码并将其保存为可执行的文件,或将其包装到要源的函数中。

一旦在您的环境中设置了zed,在您的终端中键入zed将创建一个新的,您可以根据需要编辑命令行。

若要在你的zsh环境中编辑特定的函数,你可以使用zed -f

1zed -f precmd

请注意,用zed对您的函数所做的更改只会存在,直到您通过source.zshrc或类似的命令重新加载配置文件。

变种

「vared」允许您使用您偏好的文本编辑器修改变量的值,而不是使用设置或导出命令手动在命令行上设置该值. 要使用「vared」命令,您首先需要指定您想要编辑的变量的名称。

1vared EDITOR

这将使用ZLE打开EDITOR环境变量的值,在那里您可以根据需要编辑该值。

Vidir编辑目录名称

Vidir允许您在默认编辑器中编辑目录(或文件)的名称,这有助于快速视觉地修改目录名称,而不是依靠模式匹配或循环。

1vidir "$HOME"

Vidir将创建一个包含您的$HOME目录中的每个项目的名称的临时文件.在您更改并保存文件后,vidir将更新您修改的任何文件或文件夹的名称。

您还可以使用Vidir更改单个文件的名称,通过将您选择的文件名转移到vidir作为参数而不是目录。

1vidir /path/to/file.txt

在您的默认编辑器中打开file.txt,您可以随意修改文件名。

使用Vipe在管道中的编辑器

有时在默认编辑器中修改管道的内容有用,而不是依赖awksed等工具来执行管道内的文本操作。vipe命令可以插入任何管道,并允许您视觉地编辑以前的命令的结果。

下面的示例使用vipe来修改前面的curl命令的内容,然后将结果保存到名为example_modified.txt的文件中。

1curl "https://example.com" | vipe > example_modified.txt

常规表达

「zsh」提供了一套广泛的 glob 运算符,一种形式的 扩展,如果你来自一个更基本的壳,这可能是压倒性的。

我倾向于通过使用noglob壳选项完全关闭我的环境,但这不需要:

1setopt noglob

雷克斯在测试块

zsh 提供了通过 =~ 比较运算器的基本常规表达式匹配。这允许您在执行 if 块时使用常规表达式匹配文本。

1variable="file.txt"
2
3if [[ "$variable" =~ (txt|zsh) ]];
4    then
5        print "variable contains txt or zsh";
6    else
7        print "variable does not contain txt or zsh"
8fi

使用 zsh/regex

zsh/regex 模块允许您使用旗帜在测试条件下启用正规表达式. 该旗帜使用 POSIX 扩展正规表达式来匹配文本部分。

要将zsh模块加载到您的环境中,请使用zmodload命令:

1zmodload zsh/regex

一旦加载,您可以在测试块中使用-regex-match标志来应用常规表达式,例如,您可以从上面的示例中缩短if块到test一行命令。

1zmodload zsh/regex
2
3test "txt" -regex-match (txt|zsh) && echo "contains txt or zsh" || print "does not contain txt or zsh"

您可以通过打印MATCH环境变量来检查特定匹配。

1print $MATCH
2
3txt

使用zsh/PCRE

zsh/regex一样,您可以通过zmodload命令加载zsh/prce模块:

1# note: pcre must be lowercase for the module to load
2zmodload zsh/pcre

它包含三个命令:pcre_compile、pcre_study、pcre_match。

  • pcre_compile 编译一个兼容 perl 的常规表达式。
  • 此命令的选项匹配 PCREs 的选项
  • pcre_match 一个独立的命令来匹配字符串与 PCREs。
1pcre_compile -m "bark$"
2string="dog says bark"
3pcre_match -b -- $string
4print $MATCH

此外,zsh/pcre 提供了称为pcre-match的测试条件,该条件与字符串相匹配,类似于在上述zsh/regex模块中可用的regex-match旗帜。

1test "txt" -regex-match (txt|zsh) && print "contains txt or zsh" || print "does not contain txt or zsh"

回复与 Hooks 的事件

zsh Hooks 是 特殊函数在壳的执行中自动执行的特定点,例如在提示时输入命令或壳启动时。

zsh中,chpwd函数是一个特殊的函数,每当当前工作目录更改时都会自动执行,这允许您每次用户更改目录时执行某些操作或任务。

要使用chpwd函数,您首先需要在您的.zshrc文件中定义它. 要定义chpwd函数,请在配置文件中添加以下行:

1chpwd () {
2  # Your commands and actions go here
3}

您可以使用chpwd函数更新命令提示中的目录,或显示指示新工作目录的消息。

一旦您在配置文件中定义了chpwd函数,它每次更改当前工作目录时都会自动执行,您可以通过在zsh会话中更改目录并观察chpwd函数的效果来测试它。

期刊

编写一个名为周期函数时,每 n 秒就会运行包含的命令,其中秒数存储在环境变量 $PERIOD 中,例如,假设您想要运行一个函数,该函数每 6 小时下载我的标记。

使用环境变量$PERIOD来设置函数运行的频率。

1periodic() {
2
3}

预告片

当终端重新绘制时,此操作不会运行。当背景流程向当前提示发送通知时,会发生重新绘制。

作为一个更具体的例子,我的precmd函数包含一个命令,该命令将当前的目录存储在一个文件中. 这使我能够在最近使用的目录中打开一个新的终端。

1precmd () {
2  pwd >"$HOME/.zsh_reload_directory.txt" &
3}

这将当前的目录附加到我的$HOME目录中的名为.zsh_reload.txt的文件中. 要在终端加载时自动导航到此目录,请在您的.zshrc文件中添加下列命令

1# create the precmd function
2precmd () {
3  pwd >|"$HOME/.zsh_reload_directory.txt" &
4}
5
6# add the navigation command outside of the precmd function
7cd "$HOME/.zsh_reload_directory.txt"

预告片

執行後閱讀命令,但顯示命令提示之前。

「precmd」函数的典型用途是设置「$PS1」命令提示(https://ubuntu.com/tutorials/command-line-for-beginners)变量,包含有用的信息,例如您最后执行命令的时间,但您不限于更新命令提示。

历史

zshaddhistory是一个特殊的函数,每次输入命令时都会自动执行,这允许您在输入命令时执行某些操作或任务,例如在执行命令之前登录命令或修改命令。

要使用zshaddhistory,您可以将其定义为您的.zshrc文件中的函数:

1zshaddhistory () {
2  # Your commands and actions go here
3}

您可以使用zshaddhistory函数将命令登录到文件中,或在执行之前修改命令。

一旦您在配置文件中定义了zshaddhistory函数,每次输入命令时都会自动执行,您可以通过在命令中输入命令并观察zshaddhistory函数的效果来测试它。

添加新胡子

要使用add-zsh-hook函数,您首先需要定义一个自定义的绑定函数,该函数包含您想要在指定点执行的命令和操作,例如,您可以定义一个绑定函数,该函数记录在提示中输入的命令,如下:

1log_commands () {
2  print "$(date): $1" >> ~/.zsh_command_log
3}

一旦您定义了自定义绑定函数,您可以使用add-zsh-hook函数将其绑定到现有的zsh绑定函数中,例如,要将log_command函数绑定到zshaddhistory绑定函数中,每次在提示中输入命令时,可以使用以下命令:

1add-zsh-hook zshaddhistory log_commands

这将导致log_command函数每次输入命令时执行,并将命令登录到~/.zsh_command_log文件中。

1hooks-define-hook

zsh中,用于定义自定义的功能。zsh是在壳的执行中在特定点自动执行的特殊函数,例如在提示中输入命令时或壳开始时。

若要使用「hooks-defined-hook」函数,您首先需要决定何时和何处要执行自定义的hook。这将确定该hook的名称和参数。例如,如果您希望每次在提示中输入命令时执行你的hook,您可以使用「zshaddhistory」的hook,该命令是唯一的参数。

一旦你已经决定了你的链接的名称和参数,你可以使用链接定义链接函数来定义链接,例如,如果要定义一个链接,每次输入一个命令,并将该命令登录到一个文件,你可以使用以下命令:

1hooks-define-hook zshaddhistory log_command

这将定义一个名为zshaddhistory的链接,该链接需要一个单一的论点(在提示中输入的命令)。

一旦您定义了链接,您可以使用add-zsh-hook函数将函数附加到链接中,此函数将在每次触发链接时执行。

结论

在本文中,您了解了许多方法,您可以通过使用飞行的编辑器来自定义您的工作流程,应用regex来匹配变量和其他文本,并在您的环境中设置链接,以自动响应您的壳中的事件。

Published At
Categories with 技术
comments powered by Disqus