Kon*_*lph 17 bash prompt shell-script function subshell
我想在 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
}
Run Code Online (Sandbox Code Playgroud)
这将按如下方式使用(因此我需要从子shell调用函数):
PS1='$(ps_count_reset)> '
PS2='$(ps_count_inc) '
Run Code Online (Sandbox Code Playgroud)
这样,我就会有一个带编号的多行提示:
> echo 'this
1 is
2 a
3 test'
Run Code Online (Sandbox Code Playgroud)
可爱的。但由于上述限制不起作用。
一个无效的解决方案是将计数写入文件而不是变量。但是,这会在多个同时运行的会话之间产生冲突。当然,我可以将 shell 的进程 ID 附加到文件名。但我希望有一个更好的解决方案,不会让我的系统因大量文件而混乱。
mik*_*erv 15

要获得您在问题中记录的相同输出,所需要的就是:
PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '
Run Code Online (Sandbox Code Playgroud)
你不需要扭曲。这两行将在任何伪装成接近 POSIX 兼容性的 shell 中完成所有操作。
- > cat <<HD
1 > line 1
2 > line $((PS2c-1))
3 > HD
line 1
line 2
- > echo $PS2c
0
Run Code Online (Sandbox Code Playgroud)
但我喜欢这个。我想展示使这项工作变得更好的基本原理。所以我编辑了一点。我暂时/tmp保留了它,但我想我也会为自己保留它。它在这里:
cat /tmp/prompt
Run Code Online (Sandbox Code Playgroud)
ps1() { IFS=/
set -- ${PWD%"${last=${PWD##/*/}}"}
printf "${1+%c/}" "$@"
printf "$last > "
}
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '
Run Code Online (Sandbox Code Playgroud)
注意:最近了解了yash,我昨天构建了它。无论出于何种原因,它都不会使用%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 >
Run Code Online (Sandbox Code Playgroud)
$PWD每次
$PS1评估时,它都会解析并打印$PWD以添加到提示中。但是我不喜欢整个$PWD屏幕都挤满了我,所以我只想要当前路径中每个面包屑的第一个字母到当前目录,我希望看到完整的内容。像这样:
/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv >
Run Code Online (Sandbox Code Playgroud)
这里有几个步骤:
IFS=/我们将不得不拆分当前
$PWD,最可靠的方法是使用$IFSsplit 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获取设置可言,这将不会在根发生/或者在一个从去除/如/etc。
printf "$last > "
$last是我刚刚分配给我们的顶级目录的变量。所以现在这是我们的顶级目录。它打印最后一条语句是否执行。它需要一些整洁的东西>才能很好地衡量。
然后是
$PS2条件的问题。我之前展示了如何做到这一点,您仍然可以在下面找到 - 这从根本上是一个范围问题。但是还有一点,除非你想开始做一堆printf \backspaces 然后试图平衡他们的字符数......呃。所以我这样做:
PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'再次,
${parameter##expansion}节省一天。不过这里有点奇怪——我们实际上是在剥离变量时设置了变量。我们使用它的新值 - set mid-strip - 作为我们从中剥离的 glob。你看?我们##*从增量变量的头部到最后一个字符都被剥离,最后一个字符可以是[$((PS2c=0))-9]. 我们以这种方式保证不输出值,但我们仍然分配它。这很酷 - 我以前从未这样做过。但是 POSIX 也向我们保证,这是可以做到的最便携的方式。
多亏了 POSIX-specified${parameter} $((expansion))将这些定义保留在当前 shell 中,而无需我们将它们设置在单独的子 shell 中,无论我们在哪里评估它们。这就是为什么它在dash和sh一样好,因为它在bash和zsh。我们不使用依赖于 shell/终端的转义,我们让变量自行测试。这就是使可移植代码快速的原因。
其余的相当简单 - 只需在每次$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 >
Run Code Online (Sandbox Code Playgroud)
它在bashor 中的工作原理相同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
Run Code Online (Sandbox Code Playgroud)
正如我上面所说,主要问题是您需要考虑在哪里进行计算。您不会在父 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 >
Run Code Online (Sandbox Code Playgroud)
使用这种方法(在子 shell 中运行的函数),您将无法在不经过扭曲的情况下更新主 shell 进程的状态。相反,安排函数在主进程中运行。
PROMPT_COMMAND变量的值被解释为在打印PS1提示之前执行的命令。
因为PS2,没有什么可比的。但是您可以改用一个技巧:因为您要做的只是算术运算,所以您可以使用算术扩展,它不涉及子shell。
PROMPT_COMMAND='PS_COUNT=0'
PS2='$((++PS_COUNT)) '
Run Code Online (Sandbox Code Playgroud)
算术计算的结果在提示中结束。如果你想隐藏它,你可以将它作为一个不存在的数组下标传递。
PS1='${nonexistent_array[$((PS_COUNT=0))]}\$ '
Run Code Online (Sandbox Code Playgroud)