Самый длинный общий префикс двух строк в bash

У меня есть две строки. Для примера они установлены следующим образом:

string1="test toast"
string2="test test"

Я хочу найти перекрытие, начиная с начала строк. Под перекрытием я подразумеваю строку «test t» в моем примере выше.

# So I look for the command 
command "$string1" "$string2"
# that outputs:
"test t"

Если бы строки были string1="atest toast"; string2="test test", они бы не перекрывались, так как проверка начинается с начала, а "a" в начале string1.

29
задан 27.11.2019, 01:13

11 ответов

Другой основанный на питоне ответ, основанный на собственной функции модуля os.path commonprefix

#!/bin/bash
cat mystream | python -c  

Longform, это

import sys
import os
sys.stdout.write(
    os.path.commonprefix(sys.stdin.readlines()) + b'\n'
)

/! \ Примечание: весь текст потока будет загружен в память в виде строковых объектов Python, прежде чем будет обработан этим методом


Если не требуется буферизация всего потока в памяти, мы можем использовать коммуникативное свойство и проверку общности префиксов между каждой входной парой

$!/bin/bash
cat mystream | python -c  

Длинная форма

import sys
import os
prefix = None
for line in sys.stdin:
    prefix=os.path.commonprefix(
        [line] + ([prefix] if prev else [])
    )
sys.stdout.write(prefix)

Оба эти метода должны быть бинарно-безопасными, так как в них не требуется ввод / вывод данных для кодирования ascii или utf-8, если вы столкнетесь с ошибками кодирования, python 3 переименуется в sys.stdin в sys.stdin.buffer и sys.stdout в sys.stdout.buffer, который не будет автоматически декодировать / кодировать потоки ввода / вывода при использовании

import sys\nimport os\nfor line in sys.stdin:\n\tif not os.path.isfile(line.strip()):\n\t\tcontinue\n\tsys.stdout.write(line)\n') | pythoin sys.stdin:\n\tprefix=os.path.commonprefix([line] + ([prefix] if prefix else []))\nsys.stdout.write(prefix)''

Длинная форма

import sys
import os
prefix = None
for line in sys.stdin:
    prefix=os.path.commonprefix(
        [line] + ([prefix] if prev else [])
    )
sys.stdout.write(prefix)

Оба эти метода должны быть бинарно-безопасными, так как в них не требуется ввод / вывод данных для кодирования ascii или utf-8, если вы столкнетесь с ошибками кодирования, python 3 переименуется в sys.stdin в sys.stdin.buffer и sys.stdout в sys.stdout.buffer, который не будет автоматически декодировать / кодировать потоки ввода / вывода при использовании

import sys, os; sys.stdout.write(os.path.commonprefix(sys.stdin.readlines()) + b\'\\n\')'

Longform, это

import sys
import os
sys.stdout.write(
    os.path.commonprefix(sys.stdin.readlines()) + b'\n'
)

/! \ Примечание: весь текст потока будет загружен в память в виде строковых объектов Python, прежде чем будет обработан этим методом


Если не требуется буферизация всего потока в памяти, мы можем использовать коммуникативное свойство и проверку общности префиксов между каждой входной парой

$!/bin/bash
cat mystream | python -c  

Длинная форма

import sys
import os
prefix = None
for line in sys.stdin:
    prefix=os.path.commonprefix(
        [line] + ([prefix] if prev else [])
    )
sys.stdout.write(prefix)

Оба эти метода должны быть бинарно-безопасными, так как в них не требуется ввод / вывод данных для кодирования ascii или utf-8, если вы столкнетесь с ошибками кодирования, python 3 переименуется в sys.stdin в sys.stdin.buffer и sys.stdout в sys.stdout.buffer, который не будет автоматически декодировать / кодировать потоки ввода / вывода при использовании

import sys\nimport os\nfor line in sys.stdin:\n\tif not os.path.isfile(line.strip()):\n\t\tcontinue\n\tsys.stdout.write(line)\n') | pythoin sys.stdin:\n\tprefix=os.path.commonprefix([line] + ([prefix] if prefix else []))\nsys.stdout.write(prefix)''

Длинная форма

import sys
import os
prefix = None
for line in sys.stdin:
    prefix=os.path.commonprefix(
        [line] + ([prefix] if prev else [])
    )
sys.stdout.write(prefix)

Оба эти метода должны быть бинарно-безопасными, так как в них не требуется ввод / вывод данных для кодирования ascii или utf-8, если вы столкнетесь с ошибками кодирования, python 3 переименуется в sys.stdin в sys.stdin.buffer и sys.stdout в sys.stdout.buffer, который не будет автоматически декодировать / кодировать потоки ввода / вывода при использовании

1
ответ дан 27.11.2019, 01:14

Короткий вариант Grep (идея заимствована из sed one):

$ echo -e "String1\nString2" | grep -zoP '^(.*)(?=.*?\n\1)'
String

Предполагается, что строка не имеет символа новой строки. Но легко можно настроить использование любого разделителя.

Обновление от 2016-10-24: в современных версиях grep вы можете получать жалобы grep: unescaped ^ or $ not supported with -Pz, просто используйте \A вместо ^:

$ echo -e "String1\nString2" | grep -zoP '\A(.*)(?=.*?\n\1)'
String
7
ответ дан 27.11.2019, 01:14
  • 1
    Попытайтесь пропустить оптимизацию -dontoptimize. It' s возможный, что выполняется некоторая несовместимая оптимизация. – Tomik 05.03.2016, 03:23

Это можно сделать полностью внутри bash. Хотя манипулирование строками в цикле в bash является медленным, существует простой алгоритм, логарифмирующий по числу операций оболочки, поэтому чистый bash является жизнеспособным вариантом даже для длинных строк.

longest_common_prefix () {
  local prefix= n
  ## Truncate the two strings to the minimum of their lengths
  if [[ ${#1} -gt ${#2} ]]; then
    set -- "${1:0:${#2}}" "$2"
  else
    set -- "$1" "${2:0:${#1}}"
  fi
  ## Binary search for the first differing character, accumulating the common prefix
  while [[ ${#1} -gt 1 ]]; do
    n=$(((${#1}+1)/2))
    if [[ ${1:0:$n} == ${2:0:$n} ]]; then
      prefix=$prefix${1:0:$n}
      set -- "${1:$n}" "${2:$n}"
    else
      set -- "${1:0:$n}" "${2:0:$n}"
    fi
  done
  ## Add the one remaining character, if common
  if [[ $1 = $2 ]]; then prefix=$prefix$1; fi
  printf %s "$prefix"
}

Стандартный набор инструментов включает в себя cmp для сравнения двоичных файлов. По умолчанию, это указывает смещение байта первых отличающихся байтов. Существует особый случай, когда одна строка является префиксом другой: cmp создает другое сообщение в STDERR; Самый простой способ справиться с этим - взять самую короткую строку.

longest_common_prefix () {
  local LC_ALL=C offset prefix
  offset=$(export LC_ALL; cmp <(printf %s "$1") <(printf %s "$2") 2>/dev/null)
  if [[ -n $offset ]]; then
    offset=${offset%,*}; offset=${offset##* }
    prefix=${1:0:$((offset-1))}
  else
    if [[ ${#1} -lt ${#2} ]]; then
      prefix=$1
    else
      prefix=$2
    fi
  fi
  printf %s "$prefix"
}

Обратите внимание, что cmp работает с байтами, но манипулирование строками в bash работает с символами. Это имеет значение для многобайтовых локалей, например, для локалей, использующих набор символов UTF-8. Функция выше печатает самый длинный префикс байтовой строки. Чтобы обработать строки символов с помощью этого метода, мы можем сначала преобразовать строки в кодировку с фиксированной шириной. Предполагая, что набор символов локали является подмножеством Unicode, UTF-32 отвечает всем требованиям.

longest_common_prefix () {
  local offset prefix LC_CTYPE="${LC_ALL:=LC_CTYPE}"
  offset=$(unset LC_ALL; LC_MESSAGES=C cmp <(printf %s "$1" | iconv -t UTF-32)
                                           <(printf %s "$2" | iconv -t UTF-32) 2>/dev/null)
  if [[ -n $offset ]]; then
    offset=${offset%,*}; offset=${offset##* }
    prefix=${1:0:$((offset/4-1))}
  else
    if [[ ${#1} -lt ${#2} ]]; then
      prefix=$1
    else
      prefix=$2
    fi
  fi
  printf %s "$prefix"
}
8
ответ дан 27.11.2019, 01:15
  • 1
    Договорились. Я добавил еще некоторые детали к оптимизации. – Tomik 05.03.2016, 04:02
  • 2
    Вариант этого решения, работающего над символами мультибайтов, должен был бы использовать разность вместо cmp, и использующий в качестве его входа printf %s "$1" | fold -w 1. – jfg956 27.11.2019, 01:15
  • 3
    @jfgagne Не совсем, это подавило бы символы новой строки. Между прочим, мне нравится Ваше sed решение, но это doesn' t всегда работают с многострочными строками также. – Gilles 27.11.2019, 01:15

Еще один вариант, использующий GNU grep:

$ string1="test toast"
$ string2="test test"
$ grep -zPo '(.*).*\n\K\1' <<< "$string1"\n'"$string2"
test t
10
ответ дан 27.11.2019, 01:15
  • 1
    Что, если я хочу избежать их предупреждающих об отладочной сборке, я должен использовать прозащитный файл? – IgorGanapolsky 22.07.2016, 04:33

Чувак, это сложно. Это очень тривиальная задача, но я не знаю, как это сделать с оболочкой:)

вот ужасное решение:

echo "$2" | awk 'BEGIN{FS=""} { n=0; while(n<=NF) {if ($n == substr(test,n,1)) {printf("%c",$n);} n++;} print ""}' test="$1"
1
ответ дан 27.11.2019, 01:17
  • 1
    Кто-либо испытывает с обходным решением, описанным в этом ответе? – tapmonkey 28.06.2016, 21:42
  • 2
    Я думал perl ' s (eof), но можно предотвратить тот заключительный автоматический вывод OFS через задержанная обработка каждой входной строки .. Еще одна точка: Эти echo "$2" добавляет постороннее \n к $2 – Peter.O 27.11.2019, 01:18
  • 3
    Это очень быстро, как есть, но имеет несколько проблем. (1) Это doesn' t обрабатывают символы mumti-байта. Это легко фиксируется.. просто изменение %c к %s.. (2) Это сообщает неправильно, когда две строки идентичны кроме, у каждого есть запаздывание \n, и другой не делает. В этом случае сценарий сообщает о более длинном значении... Исправление запаздывающей проблемы новой строки, вероятно, так легко не фиксируется, как это - поведение awk, whch добавит запаздывающую новую строку (который вызывает проблему). Но, поскольку я пишу это, я вспоминаю, что существует способ обнаружить last-line в awk (я думаю!). I' ll проверяют теперь. – Peter.O 27.11.2019, 01:19

Если вы используете другие языки, как насчет python:

cmnstr() { python -c "from difflib import SequenceMatcher
s1, s2 = ('''$1''', '''$2''')
m = SequenceMatcher(None,s1,s2).find_longest_match(0,len(s1),0,len(s2))
if m.a == 0: print(s1[m.a: m.a+m.size])"
}
$ cmnstr x y
$ cmnstr asdfas asd
asd

(ответ h / t на @ RickardSjogren на переполнение стека 18715688 )

1
ответ дан 27.11.2019, 01:17
  • 1
    Что делает Вы подразумеваете под ' Закройтесь WKWebView '? removeFromSuperview и набор это к нолю? – Lukas Würzburger 17.11.2016, 04:10

Если у вас есть возможность установить пакет python, вы можете использовать эту утилиту python

# install pythonp
pythonp -m pip install pythonp

echo -e "$string1\n$string2" | pythonp 'l1,l2=lines
res=itertools.takewhile(lambda a: a[0]==a[1], zip(l1,l2)); "".join(r[0] for r in res)'
2
ответ дан 27.11.2019, 01:19
  • 1
    спасибо. I' ll проверяют его. У Вас есть ссылка на функцию, которые выполняют тот переключатель WKProcessPool? Я просто заменяю пул новым? – aporat 20.10.2015, 12:11

Усовершенствованная версия примера sed находит общий префикс N строк (N> = 0):

string1="test toast"
string2="test test"
string3="teaser"
{ echo "$string1"; echo "$string2"; echo "$string3"; } | sed -e 'N;s/^\(.*\).*\n\1.*$/\1\n\1/;D'

Если строки хранятся в массиве, их можно передать в sed с printf :

strings=("test toast" "test test" "teaser")
printf "%s\n" "${strings[@]}" | sed -e '$!{N;s/^\(.*\).*\n\1.*$/\1\n\1/;D;}'

Вы также можете использовать здесь здесь :

strings=("test toast" "test test" "teaser")
oIFS=$IFS
IFS= 

Здесь (как и все перенаправления) может идти куда угодно в пределах простой команды.

\n' <<<"${strings[*]}" sed -e '$!{N;s/^\(.*\).*\n\1.*$/\1\n\1/;D;}' IFS=$oIFS # for a local IFS: (IFS=

Здесь (как и все перенаправления) может идти куда угодно в пределах простой команды.

\n'; sed -e '$!{N;s/^\(.*\).*\n\1.*$/\1\n\1/;D;}' <<<"${strings[*]}")

Здесь (как и все перенаправления) может идти куда угодно в пределах простой команды.

14
ответ дан 27.11.2019, 01:20

Просто еще один способ использовать только Bash.

string1="test toast"
string2="test test"
len=${#string1}

for ((i=0; i<len; i++)); do
   if [[ "${string1:i:1}" == "${string2:i:1}" ]]; then
      continue
   else
      echo "${string1:0:i}"                       
      i=len
   fi
done
2
ответ дан 27.11.2019, 01:20

Без sed, используя утилиту cmp, чтобы получить индекс первого отличного символа, и используя подстановку процесса, чтобы получить 2 строки в cmp:

string1="test toast"
string2="test test"
first_diff_char=$(cmp <( echo "$string1" ) <( echo "$string2" ) | cut -d " " -f 5 | tr -d ",")
echo ${string1:0:$((first_diff_char-1))}
4
ответ дан 27.11.2019, 01:21
  • 1
    Хм, странный. Что относительно другого модуля ': libraries:material-drawer' это запутывается? Эта проблема часто происходит, когда запутываемая библиотека добавляется к приложению, и библиотека пропускает некоторые атрибуты. – Tomik 05.03.2016, 02:49
  • 2
    Хороший выбор инструмента, но неправильно пред - и последующая обработка. echo "$string1" искажения несколько строк и Вы don' t обрабатывают случай, когда одна из строк является префиксом другого. Вы don' t нужен вызов к cut, поскольку оболочка совершенно способна к извлечению смещения от эти cmp вывод. Ограничение этого подхода - то, что cmp воздействует на байты, не символы. – Gilles 27.11.2019, 01:22
  • 3
    @Gilles: мерси beaucoup / большое спасибо за Вас объяснения. – jfg956 27.11.2019, 01:22
  • 4
    Используя sed лучшее решение, хотя, поскольку только один процесс должен быть запущен. – jfg956 27.11.2019, 01:22
  • 5
    Под ударом, с отдельным аргументом, echo только искажения ^-[neE]+$; хотя, если эти xpg_echo установлен тогда echo также обратные косые черты искажений. Также echo добавляет новую строку, которая объясняет почему Вы didn' t видят, что foo префикс foobar: Вы передавали foo\n и foobar\n к cut. Попробуйте [1 110] или [1 111] и foo\nbar. – Gilles 27.11.2019, 01:23
  • 6
    @Gilles: Вы могли показать мне пример где echo искажения строка? В bash' s человек, я нашел пример с echo -e "toto\ntata", так будет он быть безопасным использовать echo -E (благодарит за printf пример хотя). О случае, где строка является префиксом другого, у меня нет различного вывода с cmp (GNU diffutils) 2.8.1. Верный о возможности предотвращения cut, и полностью верный о не работе над символом мультибайтов. – jfg956 27.11.2019, 01:23

Наверное, проще на другом языке. Вот мое решение:

common_bit=$(perl -le '($s,$t)=@ARGV;for(split//,$s){last unless $t=~/^\Q$z

Наверное, проще на другом языке. Вот мое решение:

[110]

Если бы это не было одной строкой, я бы использовал более длинные имена переменных, больше пробелов, больше скобок и т. Д. Я также уверен, что есть более быстрый путь, даже в Perl, но, опять же, это компромисс между скоростью и пространством: он использует меньше места на длинном однострочнике.

/;$z.=

Наверное, проще на другом языке. Вот мое решение:

[110]

Если бы это не было одной строкой, я бы использовал более длинные имена переменных, больше пробелов, больше скобок и т. Д. Я также уверен, что есть более быстрый путь, даже в Perl, но, опять же, это компромисс между скоростью и пространством: он использует меньше места на длинном однострочнике.

}print $z' "$string1" "$string2")

Если бы это не было одной строкой, я бы использовал более длинные имена переменных, больше пробелов, больше скобок и т. Д. Я также уверен, что есть более быстрый путь, даже в Perl, но, опять же, это компромисс между скоростью и пространством: он использует меньше места на длинном однострочнике.

3
ответ дан 27.11.2019, 01:22
  • 1
    @IgorGanapolsky, если you' ve явно изменил Ваш build.gradle файл, сборки отладки не используют ProGuard, таким образом, это не важно для сборок отладки. Это doesn' t имеют много смысла (мне, по крайней мере) запутывать сборки отладки, потому что он делает его тяжелее для отладки. – AlxDroidDev 26.01.2017, 15:05

Теги

Похожие вопросы