将输出管道连接到具有多个输入的bash功能

时间:2019-05-09 15:22:21

标签: bash awk pipe levenshtein-distance

这是我要尝试的操作:我想使用bash来测量两根琴弦之间的Levensthein距离。我找到了LD here的实现。

现在,假设我有一些像这样的玩具数据:

1    The brown fox jumped    The green fox jumped
0    The red fox jumped    The green fox jumped
1    The gray fox jumped    The green fox jumped

并说它存储在data.test中。

然后,我通过一个简单的awk命令将其过滤掉,以这样的方式过滤掉以1开头的行:

awk -F '\t' '{if ($1>0) print $2,t,$3}' data.test

此简单命令的第一个输出将是:

The brown fox jumped    The green fox jumped

我现在想通过将输出直接管道传递到此函数(从以上链接中删除)来测量这两个句子之间的Levensthein距离:

function levenshtein {
    if (( $# != 2 )); then
        echo "Usage: $0 word1 word2" >&2
    elif (( ${#1} < ${#2} )); then
        levenshtein "$2" "$1"
    else
        local str1len=${#1}
        local str2len=${#2}
        local d

        for i in $( seq 0 $(( (str1len+1)*(str2len+1) )) ); do
            d[i]=0
        done

        for i in $( seq 0 $str1len );   do
            d[i+0*str1len]=$i
        done

        for j in $( seq 0 $str2len );   do
            d[0+j*(str1len+1)]=$j
        done

        for j in $( seq 1 $str2len ); do
            for i in $( seq 1 $str1len ); do
                [ "${1:i-1:1}" = "${2:j-1:1}" ] && local cost=0 || local cost=1
                del=$(( d[(i-1)+str1len*j]+1 ))
                ins=$(( d[i+str1len*(j-1)]+1 ))
                alt=$(( d[(i-1)+str1len*(j-1)]+cost ))
                d[i+str1len*j]=$( echo -e "$del\n$ins\n$alt" | sort -n | head -1 )
            done
        done
        echo ${d[str1len+str1len*(str2len)]}
    fi
}

我知道您可以执行此操作,但是由于需要传递两个参数以及我传递序列这一事实,我陷入了困境。

我尝试使用各种版本的this建议,提倡按以下方式获取输入信息:

function levenshtein {
    # Grab input.
    declare input1=${1:-$(</dev/stdin)};
    declare input2=${2:-$(</dev/stdin)};
.
.
.
}

这是我无法正常工作的部分。

3 个答案:

答案 0 :(得分:7)

您根本不需要awk

while IFS=$'\t' read num first second; do
    [[ $num -gt 0 ]] || continue
    levenshtein "$first" "$second"
done < data.txt

(是的,awk处理大文件的速度比bash快,但是如果首先在bash中实现Levenshtein算法,则速度可能不是问题)


顺便说一句,通过使用带有“元组”作为键的关联数组,不需要进行太多索引运算的更简单(尽管经过最少测试)。

levenshtein () {
  if (( ${#1} < ${#2} )); then
    levenshtein "$2" "$1"
    return
  fi

  local str1len str2len cost m a b i j
  local -A d

  str1len=${#1}
  str2len=${#2}
  for ((i=0;i<=strlen1;i++)); do
    d[$i,0]=0
  done

  for ((j=0;j<=strlen2;j++)); do
    d[0,$j]=0
  done

  for ((j=1; j<=str2len; j++)); do
    for ((i=1; i<=str1len; i++)); do
      a=${1:i-1:1}
      b=${2:j-1:1}
      [ "$a" = "$b" ] && cost=0 || cost=1
      del=$(( $d[$((i-1)),$j] + 1 ))
      ins=$(( $d[$i,$((j-1))] + 1 ))
      alt=$(( $d[$((i-1)),$((j-1))] + cost ))

      # Compute the min without forking
      m=$del; ((ins < m)) && m=$ins; ((alt < m)) && m=$alt

      d[$i,$j]=$m
    done
  done
  echo ${d[$str1len,$str2len]}
} 

答案 1 :(得分:1)

如果在用export -f levenshtein调用awk之前以bash导出Levenshtein函数,则可以轻松地逐行在awk中调用该函数: awk -F '\t' '$1>0 {system("levenshtein \""$2"\" \""$3"\"")}'

答案 2 :(得分:1)

我的支持者是Chepner的答案,但是如果由于某种原因您发现自己陷在实际需要解决的地方,那也不难。

# Awk script refactored slightly for aesthetics
pair=$(awk -F '\t' '$1>0 {print $2 "\t" $3}' data.test)
levenshtein "${pair%$'\t*'}" "${pair#$'*\t'}"

稍微打开包装;

  • levenshtein的两个参数用双引号引起来。
  • 每个参数由一个参数替换组成;
    • ${variable%pattern}产生variable的值,并删除所有与pattern相匹配的后缀
    • ${variable#pattern}产生variable的值,并删除与pattern匹配的任何前缀
    • 这两个都匹配最短的pattern。如果您有一个包含多个字段的字符串,则可能需要##%%变体,它们分别从值的前面或后面修剪最长的适用pattern
  • $'\t'是C样式的字符串,其中包含制表符
  • pattern在选项卡的前面或后面还包含一个*,用于删除该选项卡之前或之后的所有内容,以根据需要从选项卡分隔的字符串中获取第一个或第二个值。