Передача вывода в функцию bash с несколькими входами - PullRequest
3 голосов
/ 09 мая 2019

Вот что я пытаюсь сделать: я хочу измерить расстояние Левенштейна между двумя строками, используя bash. Я нашел реализацию LD здесь .

Теперь предположим, что у меня есть данные игрушек, например:

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

Теперь я хочу измерить расстояние Левенштейна между этими двумя предложениями, передавая этот вывод непосредственно в эту функцию (снято с ссылки выше):

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
}

Я знаю, что вы можете сделать это, но я застреваю, потому что есть два аргумента, которые нужно передать, и тот факт, что я передаю последовательности.

Я пытался использовать различные версии этого предложения, которое поощряет захват ввода как таковой:

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

Это та часть, которую я не могу заставить работать.

Ответы [ 3 ]

7 голосов
/ 09 мая 2019

Вам вообще не нужно awk:

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

(Правда, awk быстрее при обработке большого файла, чем bash, но если вы реализуете алгоритм Левенштейна в bash, во-первых, скорость, вероятно, не имеет значения.)


Кроме того, более простая (хотя и минимально протестированная) реализация, которая не требует такой большой арифметики индекса с использованием ассоциативного массива с «кортежами» в качестве ключей.

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 голос
/ 09 мая 2019

Мой ответ идет на ответ Чепнера, но если по какой-то причине вы застряли в месте, где вам действительно нужно решить эту проблему, это тоже не сложно.

# 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 также содержит * перед или за вкладкой, чтобы удалить все до или после вкладки, как требуетсячтобы получить только первое или второе значение из строки, разделенной табуляцией.
1 голос
/ 09 мая 2019

Если вы экспортируете функцию Левенштейна в bash перед вызовом awk с помощью export -f levenshtein, вы можете легко вызывать функцию в awk построчно: awk -F '\t' '$1>0 {system("levenshtein \""$2"\" \""$3"\"")}'.

...