我想在 Bash 中实现一个函数,每次调用都会增加(并返回)计数。不幸的是,这似乎并不简单,因为我在子 shell 内调用该函数,因此它无法修改其父 shell 的变量。
这是我的尝试:
PS_COUNT=0
ps_count_inc() {
let PS_COUNT=PS_COUNT+1
echo $PS_COUNT
}
ps_count_reset() {
let PS_COUNT=0
}
这将按如下方式使用(因此我需要从子 shell 调用函数):
PS1='$(ps_count_reset)> '
PS2='$(ps_count_inc) '
这样,我就会得到一个编号的多行提示:
> echo 'this
1 is
2 a
3 test'
可爱的。但由于上述限制不起作用。
一个无效的解决方案是将计数写入文件而不是变量。但是,这会在多个同时运行的会话之间产生冲突。当然,我可以将 shell 的进程 ID 附加到文件名中。但我希望有一个更好的解决方案,不会因大量文件而使我的系统混乱。
答案1
要获得与您在问题中注意到的相同的输出,所需要做的就是:
PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '
你不需要扭曲。这两行代码将在任何伪装成接近 POSIX 兼容性的 shell 中完成这一切。
- > cat <<HD
1 > line 1
2 > line $((PS2c-1))
3 > HD
line 1
line 2
- > echo $PS2c
0
但我喜欢这个。我想展示使这项工作变得更好的基本原理。所以我对此进行了一些编辑。我暂时把它放进去/tmp
,但我想我也会为自己保留它。它在这里:
cat /tmp/prompt
提示脚本:
ps1() { IFS=/
set -- ${PWD%"${last=${PWD##/*/}}"}
printf "${1+%c/}" "$@"
printf "$last > "
}
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '
注:最近了解到亚什,我昨天建的。无论出于何种原因,它都不会使用字符串打印每个参数的第一个字节%c
- 尽管文档特定于该格式的宽字符扩展,因此它可能相关 - 但它确实可以%.1s
这就是全部事情。那里发生了两件主要的事情。这就是它的样子:
/u/s/m/man3 > cat <<HERE
1 > line 1
2 > line 2
3 > line $((PS2c-1))
4 > HERE
line 1
line 2
line 3
/u/s/m/man3 >
解析$PWD
每次
$PS1
评估时,它都会解析并打印$PWD
以添加到提示中。但我不喜欢整个$PWD
屏幕拥挤,所以我只想要当前路径中每个面包屑的第一个字母到当前目录,我想看到完整的目录。像这样:
/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv >
这里有几个步骤:
IFS=/
我们将不得不分割电流
$PWD
,最可靠的方法是使用$IFS
split on/
。之后根本不需要费心——从这里开始的所有分割都将由$@
下一个命令中 shell 的位置参数数组定义,例如:
set -- ${PWD%"${last=${PWD##/*/}}"}
所以这个有点棘手,但最主要的是我们在符号
$PWD
上进行了分裂。/
我还使用参数扩展来分配$last
最左边和最右边/
斜杠之间出现的任何值之后的所有内容。这样我就知道,如果我只是在/
并且只有一个,/
那么$last
仍然等于整体$PWD
并且$1
是空的。这很重要。在将其分配给 之前,我还$last
从 的尾部剥离。$PWD
$@
printf "${1+%c/}" "$@"
所以在这里 - 只要
${1+is set}
我们每个 shell 参数的printf
第一个%c
字符 - 我们刚刚将其设置为当前目录中的每个目录$PWD
- 减去顶级目录 - 就可以分割/
。因此,我们本质上只是打印$PWD
除顶部目录之外的每个目录的第一个字符。但重要的是要认识到这种情况只有在$1
被设置时才会发生,而这不会发生在 root/
或从/
诸如/etc
.
printf "$last > "
$last
是我刚刚分配给我们的顶级目录的变量。现在这是我们的顶级目录。它打印最后一条语句是否执行。>
要想达到良好的效果,需要一点点整洁。
但增量又如何呢?
然后是条件问题
$PS2
。我之前展示了如何做到这一点,您仍然可以在下面找到它 - 这从根本上来说是一个范围问题。但还有更多的内容,除非你想开始做一堆printf \b
ackspace,然后尝试平衡它们的字符数......呃。所以我这样做:
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
再次
${parameter##expansion}
拯救了这一天。不过这里有点奇怪 - 我们实际上是在删除变量本身的同时设置了变量。我们使用它的新值 - set mid-strip - 作为我们从中剥离的 glob。你看?我们##*
删除从增量变量的头部到最后一个字符的所有字符,可以是 中的任何字符[$((PS2c=0))-9]
。通过这种方式,我们保证不输出该值,但我们仍然分配它。这很酷——我以前从未这样做过。但 POSIX 也向我们保证这是最可移植的方式。
这要归功于 POSIX 规定${parameter} $((expansion))
,将这些定义保留在当前 shell 中,而不需要我们将它们设置在单独的子 shell 中,无论我们在哪里评估它们。这就是为什么它在dash
和中的效果与在和 中的sh
效果一样好。我们不使用 shell/终端相关的转义符,并且让变量自行测试。这就是可移植代码的原因bash
zsh
快的。
其余的相当简单 - 只需在每次$PS2
评估时增加我们的计数器,直到$PS1
再次重置它。像这样:
PS2='$((PS2c=PS2c+1)) > '
所以现在我可以:
达世币演示
ENV=/tmp/prompt dash -i
/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cat <<HERE
1 > line 1
2 > line 2
3 > line $((PS2c-1))
4 > HERE
line 1
line 2
line 3
/u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c"
$(ps1)${PS2c##*[$((PS2c=0))-9]}
$((PS2c=PS2c+1)) >
0
/u/s/m/man3 > cd ~
/h/mikeserv >
上海演示
bash
它在或中的工作原理相同sh
:
ENV=/tmp/prompt sh -i
/h/mikeserv > cat <<HEREDOC
1 > $( echo $PS2c )
2 > $( echo $PS1 )
3 > $( echo $PS2 )
4 > HEREDOC
4
$(ps1)${PS2c##*[$((PS2c=0))-9]}
$((PS2c=PS2c+1)) >
/h/mikeserv > echo $PS2c ; cd /
0
/ > cd /usr/share
/u/share > cd ~
/h/mikeserv > exit
正如我上面所说,主要问题是您需要考虑在哪里进行计算。您无法在父 shell 中获取状态 - 因此您不会在那里进行计算。您可以在子 shell 中获取状态 - 这就是您进行计算的地方。但是您在父 shell 中进行定义。
ENV=/dev/fd/3 sh -i 3<<\PROMPT
ps1() { printf '$((PS2c=0)) > ' ; }
ps2() { printf '$((PS2c=PS2c+1)) > ' ; }
PS1=$(ps1)
PS2=$(ps2)
PROMPT
0 > cat <<MULTI_LINE
1 > $(echo this will be line 1)
2 > $(echo and this line 2)
3 > $(echo here is line 3)
4 > MULTI_LINE
this will be line 1
and this line 2
here is line 3
0 >
答案2
使用这种方法(在子 shell 中运行的函数),您将无法在不进行扭曲的情况下更新主 shell 进程的状态。相反,安排该函数在主进程中运行。
的价值PROMPT_COMMAND
变量被解释为在打印提示之前执行的命令PS1
。
对于PS2
,没有什么可比的。但您可以使用一个技巧:由于您想要做的只是算术运算,因此您可以使用算术扩展,它不涉及子 shell。
PROMPT_COMMAND='PS_COUNT=0'
PS2='$((++PS_COUNT)) '
算术计算的结果最终出现在提示符中。如果你想隐藏它,你可以将它作为不存在的数组下标传递。
PS1='${nonexistent_array[$((PS_COUNT=0))]}\$ '
答案3
这有点 I/O 密集型,但您需要使用临时文件来保存计数值。
ps_count_inc () {
read ps_count < ~/.prompt_num
echo $((++ps_count)) | tee ~/.prompt_num
}
ps_count_reset () {
echo 0 > ~/.prompt_num
}
如果您担心每个 shell 会话需要一个单独的文件(这似乎是一个小问题;您真的会同时在两个不同的 shell 中键入多行命令吗?),您应该mktemp
为每个 shell 创建一个新文件使用。
ps_count_reset () {
rm -f "$prompt_count"
prompt_count=$(mktemp)
echo 0 > "$prompt_count"
}
ps_count_inc () {
read ps_count < "$prompt_count"
echo $((++ps_count)) | tee "$prompt_count"
}
答案4
你不能以这种方式使用 shell 变量,您已经明白为什么了。子 shell 继承变量的方式与进程继承其环境的方式完全相同:所做的任何更改都适用仅有的对于它及其子进程,而不是任何祖先进程。
根据其他答案,最简单的事情是将数据存储在文件中。
echo $count > file
count=$(<file)
ETC。