简体   繁体   English

ANSI 转义序列在被脚本调用时中断

[英]ANSI escape sequences breaking when called by a script

I've been using a program called fzf for selecting things in scripts for some time but on several occasions I have found myself trying to run these scripts on systems which does not have this program installed.一段时间以来,我一直在使用一个名为 fzf 的程序来选择脚本中的内容,但有几次我发现自己试图在没有安装该程序的系统上运行这些脚本。

To remedy this I wrote a mini clone (But obviously far slower on large data sets) I can embed in my scripts to avoid the dependency entirely.为了解决这个问题,我编写了一个迷你克隆(但在大型数据集上显然要慢得多)我可以嵌入到我的脚本中以完全避免依赖。

It uses ANSI escape sequences to "summon a second terminal" (I don't remember what it's called) and avoid filling the regular one with junk.它使用 ANSI 转义序列来“召唤第二个终端”(我不记得它叫什么了)并避免用垃圾填充常规终端。 The script works fine if I call it manually, but it completely breaks if I call it in a subshell or function.如果我手动调用它,该脚本工作正常,但如果我在子外壳或 function 中调用它,它会完全中断。 It also spits the escape sequences into the next program if I pipe the output or run it in a subshell.如果我 pipe output 或在子shell中运行它,它还会将转义序列吐到下一个程序中。

At this point I have spent many hours trying to debug it and I'm not sure how to continue.在这一点上,我花了很多时间尝试调试它,但我不确定如何继续。 Is there some other way I am supposed to use these sequences if called in a script.如果在脚本中调用,是否还有其他方式我应该使用这些序列。

Here is the code:这是代码:

#!/bin/bash
# FZY: Command Line Fuzzy Finder
# Created: 29/10/2020
# Author: Nan0Scho1ar (Christopher Mackinga)
# License: MIT License


input="$(< /dev/stdin)"; height="$(tput lines)"; inum=$(echo "$input" | wc -l)
echo -e "\e[?1049h" 1>&2; row=1; col=1; str=""; cur=1; fnum=$inum; regex=""; regex2=""
while true; do
    range="$row,$((row+height-3))p;$((row+height-3))q"
    filtered=$(echo "$input" | grep ".*$regex" | sed -n $range 2>/dev/null | sed -e 's/.*/  &/');
    frange="$(echo "$filtered" | wc -l)"
    curpos=$((frange-cur+1))
    echo "$filtered" | cut -c$col- | grep -E --color=always "$regex2" | tac | sed $curpos's/^  \(.*\)/> \1/'
    echo "  $fnum/$inum" && read -r -p "> $str" -n 1 -s < /dev/tty && read -r -sn3 -t 0.001 k1 < /dev/tty; REPLY+=$k1;
    case "$REPLY" in
        '') echo -e "\e[?1049l" 1>&2; echo "$filtered" | sed -n "${cur}s/  //p;${cur}q;" && exit;;
        $'\e[C'|$'\e0C') col=$((col+1));;
        $'\e[D'|$'\e0D') [[ $col -gt 1 ]] && col=$((col-1));;
        $'\e[B'|$'\e0B') [[ $cur -ge 1 ]] && cur=$((cur-1));;
        $'\e[A'|$'\e0A') [[ $cur -le $fnum ]] && cur=$((cur+1));;
        $'\e[1~'|$'\e0H'|$'\e[H') row=1;;
        $'\e[4~'|$'\e0F'|$'\e[F') row=$fnum;;
        *)
            char=$(echo "$REPLY" | hexdump -c | awk '{ print $2 }');
            if [[ $char = "033" ]]; then echo -e "\e[?1049l" 1>&2 && break;
            elif [[ $char = "177" ]] && [[ ${#str} -gt 0 ]]; then str="${str::-1}";
            else str="$str$REPLY" && row=1; fi
            regex=$(echo "$str" | sed "s/\(.\)/\1.*/g");
            regex2=$(echo "$str" | sed "s/\(.\)/\1|/g");
            fnum=$(echo "$input" | grep -c ".*$regex")
        ;;
    esac
    [[ $((frange-cur+1)) -lt 1 ]] && row=$((row+1)) && cur=$((cur-1));
    [[ $cur -lt 1 ]] && row=$((row-1)) && cur=$((cur+1));
    [[ $cur -gt $fnum ]] && cur=$fnum;
    [[ $((row-fnum+frange)) -gt 1 ]] && row=$((row-1))
    [[ $row -lt 1 ]] && row=1;
    yes '' | sed "${height}q";
done

An example of a usage which works:一个有效的用法示例:

echo 'test 1
test 2
test 3' | fzy

An example of a usage which does not work:不起作用的用法示例:

output=$(echo 'test 1
test 2
test 3' | fzy)

EDIT: As raised in comments, not capturing the output in from the subshell was not representative of my actual use case.编辑:正如评论中提出的,不从子外壳中捕获 output 并不代表我的实际用例。 Updated example failing case.更新示例失败案例。

EDIT2: redirecting control sequences to stderr seems to have improved things. EDIT2:将控制序列重定向到 stderr 似乎有所改善。 updated code to reflect this change.更新代码以反映此更改。

The solution to my problem was to send all the control sequences and menu/UI related lines to stderr as Charles Duffey suggested in the comments.正如Charles Duffey在评论中建议的那样,我的问题的解决方案是将所有控制序列和菜单/UI 相关行发送到 stderr。 All credit to him for pointing this out.所有的功劳归功于他指出这一点。

Here is the final code with the changes applied.这是应用了更改的最终代码。 (There are a still a couple minor bugs but they are completely unrelated to this question.) (还有一些小错误,但它们与这个问题完全无关。)

Note a few things such as echo -ne "\e[?1049h" were changed to echo -ne "\e[?1049h" 1>&2请注意一些内容,例如echo -ne "\e[?1049h"已更改为echo -ne "\e[?1049h" 1>&2

#!/bin/bash
# FZY: Command Line Fuzzy Finder
# Created: 29/10/2020
# Author: Nan0Scho1ar (Christopher Mackinga)
# License: MIT License

input="$(< /dev/stdin)"; height="$(tput lines)"; inum=$(echo "$input" | wc -l)
echo -ne "\e[?1049h\r" 1>&2; row=1; col=1; str=""; cur=1; fnum=$inum; regex=""; regex2=""
while true; do
    range="$row,$((row+height-3))p;$((row+height-3))q"
    filtered=$(echo "$input" | grep ".*$regex" | sed -n $range 2>/dev/null | sed -e 's/^.*/  &/');
    frange="$(echo "$filtered" | wc -l)"
    curpos=$((frange-cur+1))
    echo "$filtered" | cut -c$col- | grep -E --color=always "$regex2" | tac | sed $curpos's/^  \(.*\)/> \1/' 1>&2
    echo "  $fnum/$inum" 1>&2 && read -r -p "> $str" -n 1 -s < /dev/tty && read -r -sn3 -t 0.001 k1 < /dev/tty; REPLY+=$k1;
    case "$REPLY" in
        '') echo -ne "\e[?1049l" 1>&2; echo "$filtered" | sed -n "${cur}s/  //p;${cur}q;" && exit;;
        $'\e[C'|$'\e0C') col=$((col+1));;
        $'\e[D'|$'\e0D') [[ $col -gt 1 ]] && col=$((col-1));;
        $'\e[B'|$'\e0B') [[ $cur -ge 1 ]] && cur=$((cur-1));;
        $'\e[A'|$'\e0A') [[ $cur -le $fnum ]] && cur=$((cur+1));;
        $'\e[1~'|$'\e0H'|$'\e[H') row=1;;
        $'\e[4~'|$'\e0F'|$'\e[F') row=$fnum;;
        *)
            char=$(echo "$REPLY" | hexdump -c | awk '{ print $2 }');
            if [[ $char = "033" ]]; then echo -e "\e[?1049l" 1>&2 && exit 1;
            elif [[ $char = "177" ]] && [[ ${#str} -gt 0 ]]; then str="${str::-1}";
            else str="$str$REPLY" && row=1; fi
            regex=$(echo "$str" | sed "s/\(.\)/\1.*/g");
            regex2=$(echo "$str" | sed "s/\(.\)/\1|/g");
            fnum=$(echo "$input" | grep -c ".*$regex")
        ;;
    esac
    [[ $((frange-cur+1)) -lt 1 ]] && row=$((row+1)) && cur=$((cur-1));
    [[ $cur -lt 1 ]] && row=$((row-1)) && cur=$((cur+1));
    [[ $cur -gt $fnum ]] && cur=$fnum;
    [[ $((row-fnum+frange)) -gt 1 ]] && row=$((row-1))
    [[ $row -lt 1 ]] && row=1;
    yes '' | sed "${height}q" 1>&2;
done

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM