A.P*_*.P. 11 command-line shell less zsh
我希望能够包装一个命令,这样如果它的输出不适合终端,它将自动通过寻呼机传送。
现在我正在使用以下 shell 函数(在 zsh 中,在 Arch Linux 下):
export LESS="-R"
RET="$($@)"
RET_LINES="$(echo "${RET}" | wc -l)"
if [[ $RET_LINES -ge $LINES ]]; then
echo "${RET}" | ${PAGER:="less"}
else
echo "${RET}"
fi
Run Code Online (Sandbox Code Playgroud)
但这并不能真正说服我。有没有更好的方法(在健壮性和开销方面)来实现我想要的?我也对 zsh 特定的代码持开放态度,如果它可以很好地完成工作。
更新:自从我问了这个问题后,我找到了一个答案,它提供了一个更好的(如果更复杂)解决方案,它在$LINES将输出输送到less而不是全部缓存之前缓冲大多数行。可悲的是,这也不是很令人满意,因为这两种解决方案都没有考虑过长的包裹线。例如,如果上面的代码存储在一个名为 的函数中pager_wrap,那么
pager_wrap echo {1..10000}
Run Code Online (Sandbox Code Playgroud)
将很长的一行打印到标准输出,而不是通过寻呼机进行管道传输。
我有一个针对 POSIX shell 合规性编写的解决方案,但我只在 bash 中对其进行了测试,所以我不确定它是否可移植。而且我不知道 zsh,所以我没有尝试让它对 zsh 友好。你用管道把你的命令放进去;将命令作为参数传递给另一个命令是一个糟糕的设计*。
当然,这个问题的任何解决方案都需要知道终端有多少行和列。在下面的代码中,我假设您可以依赖LINES和COLUMNS环境变量(less查看)。更可靠的方法是:
rows="${LINES:=$(tput lines)}"
and ,或cols="${COLUMNS:=$(tput cols)}"stty size. 请注意,此命令必须将终端作为其标准输入,因此,如果它在脚本中,并且您正在通过管道输入脚本,则必须说stty size <&1(在 bash 中)或stty size < /dev/tty. 捕获其输出甚至更加复杂。秘诀:fold命令会像屏幕一样打断长行,因此脚本可以正确处理长行。
#!/bin/sh
buffer=$(mktemp)
rows="$LINES"
cols="$COLUMNS"
while true
do
IFS= read -r some_data
e=$? # 1 if EOF, 0 if normal, successful read.
printf "%s" "$some_data" >> "$buffer"
if [ "$e" = 0 ]
then
printf "\n" >> "$buffer"
fi
if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ]
then
if [ "$e" != 0 ]
then
cat "$buffer"
else
continue
fi
else
if [ "$e" != 0 ]
then
"${PAGER:="less"}" < "$buffer"
# The above is equivalent to
# cat "$buffer" | "${PAGER:="less"}"
# … but that’s a UUOC.
else
cat "$buffer" - | "${PAGER:="less"}"
fi
fi
break
done
rm "$buffer"
Run Code Online (Sandbox Code Playgroud)
要使用这个:
mypager。$HOME/bin。chmod +x mypager。ps ax | mypager或之类的命令中使用它ls -la | mypager。ps ax | path_to_mypager/mypagerpath_to_mypager.Unix 的哲学是“ Do One Thing and Do It Well”。例如,如果程序要以某种方式显示数据(如寻呼机那样),那么它不应该调用产生数据的机制。这就是管道的用途。
执行用户指定的命令或程序的 Unix 程序并不多。让我们看看一些这样做的:
sh -c "command"env,nice,nohup,setsid,su,和sudo。这些程序有一些共同点——它们的存在都是为了运行具有修改过的执行环境1 的程序。它们必须按照它们的方式工作,因为 Unix 通常不允许您更改另一个进程的执行环境;您必须更改自己的流程,然后fork和/或exec. nice”值、UID 和 GID、进程组、会话 ID、控制终端、打开文件、工作目录,umask值,ulimits,信号处置,alarm 定时器等vi/ vim,尽管我很确定还有其他例子。这些都是历史文物。它们早于窗口系统甚至作业控制;如果您正在编辑一个文件,并且想要做其他事情(例如查看目录列表),则必须保存文件并退出编辑器才能返回到您的 shell。现在你可以切换到另一个窗口,或者使用Ctrl+ Z(或 type :suspend)返回到你的 shell,同时保持你的编辑器活着,所以 shell 转义可以说已经过时了。我不计算执行其他(硬编码)程序以利用其功能而不是复制它们的程序。例如,某些程序可能会执行diff或sort。(例如,有一些故事,早期版本spell
用于sort?-u获取文档中使用的单词列表,然后diff——或者可能comm——将该列表与字典单词列表进行比较,并确定文档中的哪些单词不在词典。)
按照脚本的编写方式,RET="$($@)"在调用的命令完成之前,该行不会完成。因此,在生成它的命令完成之前,您的脚本无法开始显示数据。解决这个问题的最简单方法可能是将数据生成命令与数据显示程序分开(尽管还有其他方法)。
假设您使用显示过滤器处理的输出运行一些命令,然后查看输出,并决定将该输出保存在文件中。如果你输入了(作为一个假设的例子)
#!/bin/sh
buffer=$(mktemp)
rows="$LINES"
cols="$COLUMNS"
while true
do
IFS= read -r some_data
e=$? # 1 if EOF, 0 if normal, successful read.
printf "%s" "$some_data" >> "$buffer"
if [ "$e" = 0 ]
then
printf "\n" >> "$buffer"
fi
if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ]
then
if [ "$e" != 0 ]
then
cat "$buffer"
else
continue
fi
else
if [ "$e" != 0 ]
then
"${PAGER:="less"}" < "$buffer"
# The above is equivalent to
# cat "$buffer" | "${PAGER:="less"}"
# … but that’s a UUOC.
else
cat "$buffer" - | "${PAGER:="less"}"
fi
fi
break
done
rm "$buffer"
Run Code Online (Sandbox Code Playgroud)
然后你可以输入
ps ax | mypager
Run Code Online (Sandbox Code Playgroud)
或按↑并适当编辑该行。现在,如果你输入了
!:1 > myfile
Run Code Online (Sandbox Code Playgroud)
您仍然可以返回并将该命令编辑为ps ax > myfile,但这并不那么简单。
或者假设您决定要运行ps uax下一个。如果你已经打字ps ax | mypager,你可以做
mypager "ps ax"
Run Code Online (Sandbox Code Playgroud)
同样,使用mypager "ps ax",它仍然可行,但可以说更难。
另外,看看这两个命令:ps ax | mypager和mypager "ps ax"。假设您在一history小时后运行列表。ISTM,您必须mypager "ps ax"更努力地查看正在执行的命令是什么。
echo {1..10000}显然只是一个示例命令;
ps ax并没有好多少。如果你想要做的事情只是一个小一点更逼真,像ps ax | grep oracle?如果你输入
!:0 u!:*
Run Code Online (Sandbox Code Playgroud)
它将运行mypager ps ax
并通过管道将输出从grep oracle. 因此,如果 from 的输出ps ax是 30 行长,即使 from 的输出只有 3 行,
mypager也会调用。可能有一些例子会以更戏剧性的方式失败。lessps ax | grep oracle
所以你必须做我之前展示的:
mypager ps ax | grep oracle
Run Code Online (Sandbox Code Playgroud)
但是RET="$($@)"不能处理。当然,有一些方法可以处理这样的事情,但不鼓励这样做。
如果要捕获其输出的命令行更加复杂怎么办?例如,
命令1 " arg 1 " | 命令2 ' arg 2 ' $' arg 3 '
这里的参数包含空格,制表符,凌乱的组合
$,|,\,<,>,*,;,&,[,],(,),`,,甚至'和"。像这样的命令可能很难直接正确地输入到 shell 中。现在想象一下必须引用它才能将它作为参数传递给mypager.
这就是 选项-F的用途less,尽管您也需要使用该-X选项,否则它会将文本打印到具有该选项的终端上的备用屏幕(这意味着退出后将无法随时使用该选项less)。这在未来可能会改变,因为目前有一个增强请求,要求当文本适合一个屏幕上时暗示 -X -F(303),并且RedHat 系统显然自 2008 年以来已经为此提供了补丁(尽管它还没有发布到上游(截至 2017 年 9 月 14 日,我刚刚向 bug-less@gnu.org 发送了一封关于此事的邮件))。
所以:
cmd | less -RXF
Run Code Online (Sandbox Code Playgroud)
如果输出太长时您仍然想使用备用屏幕,那么您需要花点心思(在没有上述 RedHat 补丁的系统上):
page() {
L=${LINES:-$(tput lines)} C=${COLUMNS:-$(tput cols)} \
perl -Mopen=locale -MText::Tabs -MText::CharWidth=mbswidth -e '
while(<STDIN>) {
if ($pager) {
print $pager $_;
} else {
chomp(my $line = $_);
$line =~ s/\e\[[\d;]*m//g;
$l += 1 + int(mbswidth(expand($line)) / $ENV{C});
$buf .= $_;
if ($l > $ENV{L}) {
open $pager, "|-", "less", "-R", @ARGV or die "pager: $!";
print $pager $buf;
}
}
}
print $buf unless $pager;' -- "$@"
}
Run Code Online (Sandbox Code Playgroud)
用作:
cmd | page
Run Code Online (Sandbox Code Playgroud)
或者
page < file
page -S < file...
Run Code Online (Sandbox Code Playgroud)
(不是page file,它只是为了分页标准输入)。
我们试图通过剥离颜色转义序列、展开选项卡并计算宽度来猜测输出的长度,以便我们可以确定显示给定文本行的终端行数。
只要输出没有其他转义序列或控制/编码错误的字符,就应该可以工作。
另请注意与 RedHat 补丁的一个显着区别:对于单屏输出,输出不会经过后less处理(如^X反相视频中控制字符的渲染、用...挤压空行-s)。虽然这更接近这里所要求的,但在实践中可能不太理想。
您可能需要安装 Text::CharWidth 模块,该模块不是标准模块之一(libtext-charwidth-perlDebian 上的软件包)。