LINUX.ORG.RU

bash, сравнение двух строк, с «внутренними» разделителями

 , ,


0

2

добрый день

общая задача:

- получаем две строки
- "усекаем до общей минимальной базы"
- сравниваем

подробности:

1. строки вида A.B.C

3."символ разделитель усечки" - "точка"

4.разрядность может быть разная, [1-3] числа/буквы
например:AAA.BB.C или A.BB.CCC

5. "урезка/приведение", пример
исходные строки:
A1.B1.C1 & A2.B2

после приведения (усекается первая строка):
A1.B1 & A2.B2

6. сравнение (тут все понятно)

p.s. набегом осилить не получилось сделать коротко, не «бахыт компот» :о)

простыми операциями, типа

NUM=AAA.BBB.CCC; echo ${NUM%.*} -> AAA.BBB
можно, конечно усекать, сравнивать длины, считать кол-во точек, но получается громоздко хотелось бы локоничный код, вопрос к гуру, возможно ли?

если есть идеи, выкладывайте код, рассмотрим

спасибо

p.s. с ананимусами общаюсь только при наличие КОДА

### UPDATE ###

в итоге есть несколько решений задачи, авторы cdslow & vodz

СПАСИБО ЗА ПОМОЩЬ

...

#!/usr/bin/bash
#
# https://www.linux.org.ru/forum/development/14648385?cid=14649819
# cdslow
#
# код обратно несовместимый, только на новых вер. bash-a
#
# FALSE:
# GNU bash, version 3.1.17(2)-release (i486-slackware-linux-gnu)
# GNU bash, version 4.2.45(2)-release (i486-slackware-linux-gnu)
#
# TRUE:
# GNU bash, version 4.3.46(1)-release (x86_64-slackware-linux-gnu)
#
set -e; clear

A="AA.BB.CC"
B="AA.BB.CC.DD"

IFS='.' read -r -a AS <<< $A
IFS='.' read -r -a BS <<< $B

(( N = ${#AS[@]} < ${#BS[@]} ? ${#AS[@]} : ${#BS[@]} ))

AJ=${AS[@]:0:$N}
BJ=${BS[@]:0:$N}

AN=${A:0:${#AJ}}
BN=${B:0:${#BJ}}

echo "
$A
$B
-->
$AN
$BN
"

[[ $AN == $BN ]] && echo MATCH || echo NO MATCH

...

#!/usr/bin/bash
#
# https://www.linux.org.ru/forum/development/14648385?cid=14649819
# cdslow
#
# код обратно совместимый
#
# GNU bash, version 3.1.17(2)-release (i486-slackware-linux-gnu)
# GNU bash, version 4.2.45(2)-release (i486-slackware-linux-gnu)
# GNU bash, version 4.3.46(1)-release (x86_64-slackware-linux-gnu)
#
set -e; clear

A="AA.BB.CC"
B="AA.BB.CC.DD"

IFS='.' read -r -a AW <<<$A
IFS='.' read -r -a BW <<<$B

read -r -a AS <<<${AW[@]}
read -r -a BS <<<${BW[@]}

(( N = ${#AS[@]} < ${#BS[@]} ? ${#AS[@]} : ${#BS[@]} ))

AJ=${AS[@]:0:$N}
BJ=${BS[@]:0:$N}

AN=${A:0:${#AJ}}
BN=${B:0:${#BJ}}

echo "
$A
$B
-->
$AN
$BN
"

[[ $AN == $BN ]] && echo MATCH || echo NO MATCH

...

#!/usr/bin/bash
#
# https://www.linux.org.ru/forum/development/14648385?cid=14649396
# vodz
#
set -e; clear

S1=AAA.BB.CCC
S2=AAA.BB

#///////////////////////////////////////////////////////////////////////////////
doteq()
#///////////////////////////////////////////////////////////////////////////////
{
local f1 fold=$- IFS=. s1=$1 s2=$2
set -f
eval "set -- \$$s1"
f1=$#
eval "set -- \$$s2"

if [ $# -lt $f1 ]; then
f1=$((f1-$#))
else
f1=$(($#-f1))
s1=$s2
fi

while [ $f1 -gt 0 ]; do
eval $s1='${'$s1'%.*}'
f1=$((f1-1))
done

[ "${fold#*f}" = "$fold" ] && set +f
}
#///////////////////////////////////////////////////////////////////////////////

echo "# before:
S1=$S1
S2=$S2
"
doteq S1 S2

echo "# after:
S1=$S1
S2=$S2"

...

#!/usr/bin/bash
#
# https://www.linux.org.ru/forum/development/14648385?cid=14649396
# vodz
#
set -e; clear

S1=AAA.BBB.CCC
S2=AAA.BBB

#///////////////////////////////////////////////////////////////////////////////
doteq()
#///////////////////////////////////////////////////////////////////////////////
{
local -i f1
local fold=$- IFS=. s1=$1 s2=$2
set -f
set -- ${!s1}
f1=$#
set -- ${!s2}

if [ $# -lt $f1 ]; then
f1=f1-$#
else
f1=$#-f1
s1=$s2
fi

while ((f1-->0)); do
eval $s1='${!s1%.*}'
done

[ "${fold#*f}" = "$fold" ] && set +f
}
#///////////////////////////////////////////////////////////////////////////////

echo "# before:
S1=$S1
S2=$S2
"
doteq S1 S2

echo "# after:
S1=$S1
S2=$S2"

...

#!/usr/bin/bash
#
# https://www.linux.org.ru/forum/development/14648385?cid=14651226
# cdslow/vodz mod 
#
set -e; clear

A="AAA.BBB. .*"
A="AAA.BBB.*"
A="AAA.BBB."
A="AAA.BBB"
B="AAA.BBB.CCC"

OLD_IFS=$IFS
IFS=.

read -ra AW <<< "$A"
read -ra BW <<< "$B"

AN=${#AW[@]}
BN=${#BW[@]}

if ((AN < BN)); then
BN="${BW[*]::AN}"
AN=$A
else
AN="${AW[*]::BN}"
BN=$B
fi

IFS=$OLD_IFS

echo "
$A
$B
-->
$AN
$BN
"

[[ $AN == "$BN" ]] && echo MATCH || echo NO MATCH
★★★★

Последнее исправление: sunjob (всего исправлений: 8)
Ответ на: комментарий от sunjob

Удивительно, но факт, у меня на компьютере равны:

AA.BB.CC
AA.BB.CC
MATCH
bash --version
GNU bash, version 4.3.42(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.

cdslow ★★
()
Ответ на: комментарий от cdslow

а чем и речь! я постоянно на это натыкаются, что в переход на новые версии - не так гадок, пардон, гладок :о)

проверил/уточнил

FALSE:
GNU bash, version 3.1.17(2)-release (i486-slackware-linux-gnu)
GNU bash, version 4.2.45(2)-release (i486-slackware-linux-gnu)

TRUE:
GNU bash, version 4.3.46(1)-release (x86_64-slackware-linux-gnu)

т.е. нужно «помнить» что ваш код не обратно-совместимый, но, РАБОЧИЙ :о)

есть какие-то идеи почему/дораобтки до трушного состояния?

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 1)

Забавная тема. Поясни мне. Ты сравниваешь по две переменные одного вида -

три символа (точка три символа)*
Первый этап - приведение к одинаковому количество символоточек. Тут есть два варианта: или строки в переменных имеют одинаковое кол-во символоточек, или разное. В первом случае ты сравниваешь сходу, во втором тебе нужно привести более длинную строку к количеству символоточек более короткой. Дальше ты требуешь «обе строки» после «урезания». Теперь давай разберем кейсы.
Случай со сравнением строк с одинаковым количеством символоточек не берем. Возьмем с разным. Тут есть 3 ситуации: 1) длинная строка содержит короткую + лишние символоточки (короткая строка находится в начале длинной как, например, AA.BB.CC и AA.BB.CC.DD, 2) длинная строка содержит короткую в хвосте (AA.BB.CC и DD.AA.BB.CC), 3) длинная строка не содержит короткую (AA.BB.CC и DD.DD.CC).
Ты требуешь, чтобы урезание производилось только с конца. Значит вариант 3 уходит и остаются только два варианта - когда длинная строка не содержит короткую с начала и когда длинная строка содержит короткую с начала. Чтобы проверить на такое условие достаточно grep. Выше приводили абсолютно правильные решения за исключением того, что не исключали совпадение с конца (пардон за тавтологию).
Ну и наконец, ты требуешь для дальнейших действий две урезанные строки в случае совпадения. Но совпадение означает, что мы получили два одинаковые строки, когда отняли хвост у длинной строки. Получается, что ты требуешь одну и ту же строку два раза.
Таким образом, ты или умалчиваешь какие то важные условия, из-за которых элементарно решение через grep тебе не подходит, или ты не понимаешь логику решения задачи. Пояснишь?

anonymous
()

Вот как, на мой скромный взгляд, будет выглядеть решение (добавить только логику переноса переменной для твоих дальнейших задач)

A="AA.BB.CC"
B="AA.BB.CC.DD"
C="CC.AA.BB.CC"

egrep -o ^"$A" <<< "$B"
AA.BB.CC

egrep -o ^"$A" <<< "$C"
*пусто*
anonymous
()

не вдаваясь в спор:

# имеем две строки
S1='AAA.BB'
S2='AAA.BB.C'

# длина строки в полях (разделитель точка)
# в данном случае длина 2 и 3
L1=`echo "$S1" | tr '.' ' ' | wc -w`
L2=`echo "$S1" | tr '.' ' ' | wc -w`

# находим строку которая длиннее if [ ]; ...
# и урезаем ее до длины (в полях) короткой строки
# здесь при сравнении надо урезать вторую, а первую использовать как есть
T1="$S1"
T2=`echo "$S2" | cut -d'.' -f-${L1}`

# сравниваем Т1 и Т2 (T -- truncated string)
# ну это то тебе понятно

futurama ★★★★★
()
Ответ на: комментарий от anonymous

Вот с проверкой тебе:

#!/usr/bin/env bash

if [[ "$(echo $1 | wc -c)" -lt "$(echo $2 | wc -c)" ]]; then
    egrep -oq "$1" <<< "$2" && echo "$1 = $1"
else
    egrep -oq "$2" <<< "$1" && echo "$2 = $2"
fi

anonymous
()
Ответ на: комментарий от anonymous

Пояснишь?

да, поясню, но только по поводу «необходимости» строк, т.е. использование их «после кастрации» и логики механизма...

- в зовисимости от того, равны строки или нет, далее буду использоваться либо ОБЕ СТРОКИ, либо ОДНА

...

поэтому я не вижу смысла в

- использовании сначало операций типа "grep"
- потом "кастрация строк"
- потом все остальное

и единствено логично для меня

- ПРОСТАЯ усечка строк
- ПРОСТОЕ сравнени строк
- ПРОСТОЕ использование

ПРАВИЛЬНОЕ РЕШЕНИЕ УЖЕ «проскакивало» тут :о)

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 5)
Ответ на: комментарий от sunjob

Ну так решение с grep твоей задаче никак не противоречит же. Обе строки тебе нужны, видимо, если они одинаковы (одинаковы значит идентичны как 1 и 1, значит после сравнения, когда результат положительный, тебе короткой строки хватит,чтобы образовать вторую, если она так уж нужна), а если не одинаковы, то ты просто получаешь False или как угодно исключаешь этот вариант и идешь дальше. Чекни выше, я тебе написал простой пример. Передаешь ему переменные в виде строк в кавычках и получаешь ответ, если они равны при «урезании» более длинной.

anonymous
()
Ответ на: комментарий от anonymous
- ПРОСТАЯ усечка строк
- ПРОСТОЕ сравнени строк
- ПРОСТОЕ использование !!! УСЕЧЕННЫХ СТРОК !!!

ПРАВИЛЬНОЕ РЕШЕНИЕ УЖЕ «проскакивало» тут, т.к. есть люди, которые ВСЕ ПРАВИЛЬНО ПОНЯЛИ

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 1)
Ответ на: комментарий от sunjob

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

anonymous
()
Ответ на: комментарий от anonymous
#!/bin/sh
S1=AAA.BBB.DDD
S2=AAA.BBB.CCC

doteq() {
if [[ "$(echo $1 | wc -c)" -lt "$(echo $2 | wc -c)" ]]; then
egrep -oq "$1" <<< "$2" && echo "$1 = $1"
else
egrep -oq "$2" <<< "$1" && echo "$2 = $2"
fi
}

doteq $S1 $S2

код рабочий

sunjob ★★★★
() автор топика
Ответ на: комментарий от anonymous

код рабочий, но это был сарказм, может я что-то не так логикой своей пользуюсь? :о)

что мы получаем, если строки не равны?

#!/bin/sh
S1=AAA.BBB.DDD
S2=AAA.BBB.CCC

doteq() {
if [[ "$(echo $1 | wc -c)" -lt "$(echo $2 | wc -c)" ]]; then
egrep -oq "$1" <<< "$2" && echo "$1 = $1"
else
egrep -oq "$2" <<< "$1" && echo "$2 = $2"
fi
}

doteq $S1 $S2

echo "
S1 = $1
S2 = $2
"

на выходе мы имеем

S1 =
S2 =

СТРОКИ НУЖНЫ ЖИВЫЕ ПОСЛЕ УСЕЧЕНИЯ И СРАВНЕНИЯ?

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 5)
Ответ на: комментарий от sunjob

doteq $S1 $S2

При таком вызове функция не может изменить сами S1 и S2. Нет, оно, конечно, можно в самой функции оперировать переменными S1 и S2, но тогда и передавать значения бессмысленно.

В моём варианте есть неустранимый дефект, но это чисто языковый дефект. Нельзя использовать переменные как s1 и s2 (в одном регистре как входные так и локальные у функции, как и f1 и fold). Есть кривое и тоже не без дефекта решение: переименовать переменные в _s1 _s2 _f1 и _fold.

vodz ★★★★★
()
Ответ на: комментарий от vodz

В моём варианте есть неустранимый дефект

ни чего не понял :о) из всего сказанного... прогнал ваши функции, они делают ТО-ЧТО НАДО!

если не сложно, обьясните, что там не так? :о)

sunjob ★★★★
() автор топика
Ответ на: комментарий от sunjob

Ладно, забей. Ты перенес мой код с 3 ошибками и неправильно использовал функцию, а теперь удивляешься, что он не работает как надо. Как об стенку горох... Проехали.

anonymous
()
Ответ на: комментарий от anonymous

Ты перенес мой код с 3 ошибками

какой код был, такой и перенес

неправильно использовал функцию

как ее надо было использовать?

подробнее можно? а то все какие-то обиды, недосказанности, недописссаностиии...

код вставьте рабочий, а потому уже оби... простите, обсуждайте! если есть что обсуждать, отчего бы НЕТ?! обсудим!!! нет кода... извините... нет так нет :о) вон парни подсказали, кодом поделились, проверили - да, работает, все нормально...

как-то так... без апплодисметов :о)

Ладно, забей... Проехали.

и славно, баба с возу... (или как там англичане перевели эту пословицу? леди покидает автомобиль и он набирает скорость?!)

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 4)
Ответ на: комментарий от sunjob

Так вроде бы работает и под bash-3.2 (похоже, в старых версиях сломано чтение массивов с нестандартным разделителем):

#!/usr/bin/bash

A="AA.BB.CC"
B="AA.BB.CC.DD"

IFS='.' read -r -a AW <<<$A
IFS='.' read -r -a BW <<<$B

read -r -a AS <<<${AW[@]}
read -r -a BS <<<${BW[@]}

(( N = ${#AS[@]} < ${#BS[@]} ? ${#AS[@]} : ${#BS[@]} ))

AJ=${AS[@]:0:$N}
BJ=${BS[@]:0:$N}

AN=${A:0:${#AJ}}
BN=${B:0:${#BJ}}

echo $AN
echo $BN

[[ $AN == $BN ]] && echo MATCH || echo NO MATCH

cdslow ★★
()
Ответ на: комментарий от cdslow

да, работает, спасибо!

итого, в наличие 3 рабочие версии (две vodz и одна cdslow, точнее одна обратно совместимая, и одна только на «новый» баш)

:o)

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 1)
Ответ на: комментарий от sunjob

Если ты считаешь, что такой дешевой провокацией чего то добьешься, ты ошибаешься. Все кому надо, уже поняли, что ты из себя представляешь. Но я верующий, я тебе сочувствую.

anonymous
()
Ответ на: комментарий от anonymous

спасибо, и вам не хварать!

на счет провакаКции, уважаемый верующий, может ответите на конкретно поставленные вопросы?

p.s.

... Все кому надо, уже поняли ...

какой счастье, безумно рад! а по делу есть что сказать?

в обсуждении я не касался ни вас, ни ваших взглядов, ни вашей ориентации, ни вашей религии... и уж, увольте, и ВЫ, постарайтесь как ни будь этого не делать! и... на счет «тыканья» ... ну сам понял? надеюсь...

а теперь, еще раз, предлагаю вернуться к обсуЖдению темы!!!

Ты перенес мой код с 3 ошибками

какой код был, такой и перенес, что не так?

неправильно использовал функцию

как ее надо было использовать?

подробнее можно? код рабочий вставьте ...

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 4)
Ответ на: комментарий от anonymous
#!/bin/sh
#set -e; ###!!! пришлось отключить !!!###
clear

S1=AAA.BBB.DDD
S2=AAA.BBB.CCC

#///////////////////////////////////////////////////////////////////////////////
doteq()
#///////////////////////////////////////////////////////////////////////////////
{
if [[ "$(echo $1 | wc -c)" -lt "$(echo $2 | wc -c)" ]]; then
egrep -oq "$1" <<< "$2" && echo "$1 = $1"
else
egrep -oq "$2" <<< "$1" && echo "$2 = $2"
fi
}
#///////////////////////////////////////////////////////////////////////////////

echo "before:
$S1
$S2
"

doteq $S1 $S2

echo "after:
$S1
$S2
"

exit

строки НЕ-равные (вроде все правильно)

before:
AAA.BBB.DDD
AAA.BBB.CCC

after:
AAA.BBB.DDD
AAA.BBB.CCC

строки равные (строки не урезаны, т.е. не измененные после функции)

before:
AAA.BBB
AAA.BBB.CCC

AAA.BBB = AAA.BBB

after:
AAA.BBB
AAA.BBB.CCC

ИТОГО: проверка работает, но строки не изменяются, т.е. не урезаются после сравнения

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 4)
Ответ на: комментарий от cdslow

С кавычками у Вас не очень. Попробуйте в качестве A=«A.B. .*», то есть с пробельными и метасимволами. Зачем Вам AS и BS я вообще не понял. Так что как-то так:

#!/usr/bin/bash

A="A.B. .*"
B="AA.BB.CC.DD.EE"

IFS='.' read -r -a AW <<< "$A"
IFS='.' read -r -a BW <<< "$B"

(( N = ${#AW[@]} < ${#BW[@]} ? ${#AW[@]} : ${#BW[@]} ))

OLD_IFS=$IFS
IFS=
AW=${AW[@]:0:N}
BW=${BW[@]:0:N}
IFS=$OLD_IFS

AN=${A:0:${#AW}}
BN=${B:0:${#BW}}

echo "$AN"
echo "$BN"

[[ $AN == "$BN" ]] && echo MATCH || echo NO MATCH

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

Нагляднее вариант cdslow будет как-то так:

A="A.B. .*"
B="AA.BB.CC.DD.EE"

OLD_IFS=$IFS
IFS=.
read -ra AW <<< "$A"
read -ra BW <<< "$B"
AN=${#AW[@]}
BN=${#BW[@]}
if ((AN < BN)); then
        BN="${BW[*]::AN}"
        AN=$A
else
        AN="${AW[*]::BN}"
        BN=$B
fi
IFS=$OLD_IFS

echo "$AN"
echo "$BN"

[[ $AN == "$BN" ]] && echo MATCH || echo NO MATCH
Но вот если переписать на (d)ash, то получается такая фигня, что самому не нравится:
A="A.B. .*"
B="AA.BB.CC.DD"

doteq() {
        local IFS=. oldf=$- r=$3 s i=1 t
        set -f
        AN=$A
        BN=$B
        set -- $A
        s=$#
        set -- $B
        t=$#
        if [ $s -ne $t ]; then
                if [ $s -lt $t ]; then
                        IFS=
                        t=$s
                        r=BN
                else
                        set -- $A
                        IFS=
                        r=AN
                fi
                s=
                while [ $i -le $t ]; do
                        s=${s:+$s.}\$$i
                        i=$((i+1))
                done
                eval $r=\"$s\"
        fi
        [ "${oldf#*f}" = "$oldf" ] && set +f
}

doteq
echo "$AN"
echo "$BN"

[ "x$AN" = "x$BN" ] && echo MATCH || echo NO MATCH

vodz ★★★★★
()
Ответ на: комментарий от vodz

С кавычками у Вас не очень.

Я знаю. Интересно было придумать алгоритм, а не париться с правильным экранированием. Это одна из причин, по которой я не стал бы писать это на bash.

Зачем Вам AS и BS я вообще не понял.

AS и BS понадобились, чтобы обойти баг старых версий bash, в которых команда IFS='.' read -r -a AW <<< "$A" считывает не массив а одну переменную, при этом заменяя разделители на пробелы.

cdslow ★★
()
Ответ на: комментарий от cdslow

AS и BS понадобились, чтобы обойти баг старых версий bash

Интересно, зачем вы юзаете bash 20-летней давности? Когда там 3.1 был? В начале 2000-ных? (Для анонимусов: dash то свежий).

при этом заменяя разделители на пробелы.

Я так думаю, это не баг, а разница в синтаксисе. $VAR без кавычек по идее должно разбиваться по IFS на несколько строк, а не на одну. Разница только в том, когда срабатывает IFS, до вызова read или при его работе.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

Интересно, зачем вы юзаете bash 20-летней давности?

Это вопрос к sunjob, у него зачем-то такие версии:

FALSE:
GNU bash, version 3.1.17(2)-release (i486-slackware-linux-gnu)
GNU bash, version 4.2.45(2)-release (i486-slackware-linux-gnu)

TRUE:
GNU bash, version 4.3.46(1)-release (x86_64-slackware-linux-gnu)
cdslow ★★
()
Ответ на: комментарий от cdslow

баг старых версий bash, в которых команда IFS='.' read -r -a AW <<< «$A»

И всё же я настаиваю, что это ваша проблема. Ибо у вас было без кавычек и соответственно разное поведение для разных версий.

Ну изменили синтаксис для без кавычек. Но хорошо ли это? А с кавычками все версии сработают одинаково. И IFS как отдельная команда — полезно, ибо формировать выходную строку очень просто:

BN="${BW[*]::${#AW[@]}"
где "[*]" заюзано специально.

vodz ★★★★★
()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

И всё же я настаиваю, что это ваша проблема.

Не буду спорить, поверю, что дело в кавычках.

cdslow ★★
()
Ответ на: комментарий от cdslow

... у него зачем-то такие версии ...

ни чего сокрального, это стоковые версии баша:

sl12.2 - 3.1.17
sl14.1 - 4.2.45
sl14.2 - 4.3.46

версию для dash не проверял

СПАСИБО, ПАРНИ!

sunjob ★★★★
() автор топика
Последнее исправление: sunjob (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.