有状态的 bash 函数

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 中,无论我们在哪里评估它们。这就是为什么它在dashsh一样好,因为它在bashzsh。我们不使用依赖于 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)


Gil*_*il' 8

使用这种方法(在子 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)