【问题标题】:Bash - How to replace character at certain index (Including index zero)Bash - 如何替换某个索引处的字符(包括索引零)
【发布时间】:2020-12-24 12:09:11
【问题描述】:

我是 Bash 的初学者。我以前用 Python 做了一个 man 程序,并试图用 Bash 做一个类似的程序,但我现在被困住了。我正在尝试用在正确索引处找到的字母替换“秘密”的字符。我没有在网上找到任何对我有充分帮助的东西。

例子:

word="abcdef"
secret="______"

如果猜测是在单词的索引处找到的,那么:

guess="a"
Outcome: a_____
guess="e"
Outcome: a___e_

我非常接近:

echo $secret | sed s/./$guess/$i

但这在 index 为 0 时不起作用。

sed: -e expression #1, char 7: number option to `s' command may not be zero

【问题讨论】:

  • s///1 等中没有第 0 个匹配项。就像从 1 开始的行号一样,匹配号也从 1 开始。那么,为什么不在调用 sed 之前增加索引呢?
  • @stevesliva 谢谢老兄!我不知道为什么我以前没有尝试过。

标签: string bash sed


【解决方案1】:

Bash 对这类程序并不是特别好,但它肯定内置了字符串替换功能。

到目前为止,也许将正确的猜测保留在 $correct 中并替换所有其余的。

echo "${secret//[!$correct]/_}"

参数扩展${secret//pattern/replacement} 产生$secret 的值,pattern 上的每个匹配项都替换为replacement;模式[!abc] 匹配不是abc 的任何单个字符。 (模式前有一个斜杠,只有第一个匹配项被替换。)为此,$correct 不能为空,因此可以在程序开始时将其初始化为"_"

如果你真的想要基于索引的字符串处理,${variable:start:len}$variable 中提取一个子字符串。

这些参数扩展是特定于 Bash 的; POSIX sh 支持的操作集要少得多。

【讨论】:

    【解决方案2】:

    需要明确的是,您所询问的部分不是您会使用 bash 或任何其他 shell 的东西,所以如果您想学习在 UNIX 环境中做事的正确方法:shell 是创建/销毁文件和进程以及对工具的调用排序的环境。如果你想使用强制性的 UNIX 工具(即每个 UNIX 机器上存在的工具)来做这样的事情,那么你会使用 awk,而不是 shell。唯一的 shell 部分是调用 awk。

    鉴于此,如果您想在“bash”中编写一个hangman 程序,这是一种可以在每个 UNIX 系统上使用任何 shell 中的任何 awk 来完成提示/猜测部分的方法:

    $ cat tst.awk
    BEGIN {
        secret = word
        gsub(/./,"_",secret)
        print "\nOutcome:", secret
        printf "guess? "
    }
    {
        guess = $0
        while ( pos=index(word,guess) ) {
            secret = substr(secret,1,pos-1) guess substr(secret,pos+1)
            word   = substr(word,1,pos-1)    "_"  substr(word,pos+1)
        }
        print "\nOutcome:", secret
        if (word ~ /^_*$/) {
            exit
        }
        printf "guess? "
    }
    

    现在 bash(或任何其他 shell)部分将是:

    $ awk -v word='abcdef' -f tst.awk
    
    Outcome: ______
    guess? a
    
    Outcome: a_____
    guess? e
    
    Outcome: a___e_
    guess? d
    
    Outcome: a__de_
    guess? f
    
    Outcome: a__def
    guess? b
    
    Outcome: ab_def
    guess? c
    
    Outcome: abcdef
    

    我决定实现一个完整的 hangman shell 脚本,它会生成 5 个或更多字母的单词供你猜测,因为玩起来很有趣,所以你开始吧:

    $ cat ./hangman.sh
    #!/usr/bin/env bash
    
    declare -a words
    trap 'printf "\nwords used:\n"; printf "%s\n" "${words[@]}"; exit' 0
    
    prtwords() {
        local dfltwordfile='/usr/share/dict/words'
    
        {
            if [[ -s "$dfltwordfile" ]]; then
                cat "$dfltwordfile"
            else
                curl -s 'https://svnweb.freebsd.org/csrg/share/dict/words?view=co&content-type=text/plain'
            fi
        } |
        tr 'A-Z' 'a-z' |
        grep -E '.{5}' |
        shuf
    }
    
    guesschars() {
        awk -v word="$1" '
            BEGIN {
                secret = origWord = word
                gsub(/./,"_",secret)
                print "\nOutcome:", secret
                printf "guess? "
                maxFailures = 6
            }
            NF {
                guess = substr($1,1,1)
                isFailure = 1
                while ( pos=index(word,guess) ) {
                    isFailure = 0
                    secret = substr(secret,1,pos-1) guess substr(secret,pos+1)
                    word   = substr(word,1,pos-1)    "_"  substr(word,pos+1)
                }
                numFailures += isFailure
                print "\nOutcome:", secret
                if ( (word ~ /^_*$/) || (numFailures == maxFailures) ) {
                    print "The word was", origWord
                    exit
                }
                printf "guess? "
            }
        '
    }
    
    # See https://stackoverflow.com/a/41652573/1745001 for rationale on
    # the file descriptor manipulation below.
    
    exec 3</dev/tty || exec 3<&0            ## make FD 3 point to the TTY or stdin (as fallback)
    
    echo 'Hit interrupt to stop' >&2
    while IFS= read -r word; do             ## |- loop over lines read from FD 0
        words+=( "$word" )
        guesschars "$word" <&3              ## |- run guesschars with its stdin copied from FD 3
        echo '#####' >&2
    done < <(prtwords)                      ## \-> ...while the loop is run with prtwords output on FD 0
    
    exec 3<&-                               ## close FD 3 when done.
    

    .

    $ ./hangman.sh
    Hit interrupt to stop
    
    Outcome: __________
    guess? a
    
    Outcome: _______a__
    guess? e
    
    Outcome: _e__e__a_e
    guess? s
    
    Outcome: _e__e__a_e
    guess? t
    
    Outcome: _e__e__ate
    guess? r
    
    Outcome: _e_re__ate
    guess? c
    
    Outcome: _e_rec_ate
    guess? d
    
    Outcome: de_rec_ate
    guess? p
    
    Outcome: deprec_ate
    guess? i
    
    Outcome: depreciate
    The word was depreciate
    #####
    
    Outcome: ______
    guess?
    words used:
    depreciate
    enrico
    

    【讨论】:

    • 做得很好 - 感谢分享。您的 trap 定义中有一个不匹配的 '。应该是printf "%s\n" - 否则会得到unexpected EOF while looking for matching `"'
    • @vgersh99 所以我做到了!感谢您的关注,现已修复。
    • 这会上瘾!也很鼓舞人心。我不知道为什么这是 awk 的一个功能,但是对于空想,index(word,"") 返回 1 (!),所以 while 循环永远存在,所以 guess 应该总是有一些东西。
    • @thanasisp 你是对的,我以前从未注意到这一点。我不想花任何时间试图使这个脚本真正健壮(对于任何想要这样做的人来说都是一个有用的练习!)但是因为这个很简单 - 我调整了代码以防止有人输入多字符字符串(只取第一个字符)。
    • 感谢您的回答!是的,我知道 Bash 并不是真正为这种程序而设计的。这只是我想做的一件事,以便我可以边做边学。
    猜你喜欢
    • 2012-02-15
    • 2017-06-04
    • 1970-01-01
    • 2014-03-31
    • 2020-07-26
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多