2011-08-04 10 views
5

Por ejemplo:¿Cómo encuentro caracteres comunes entre dos cadenas en bash?

s1="my_foo" 
s2="not_my_bar" 

el resultado deseado sería my_o. ¿Cómo hago esto en bash?

+0

el guión bajo será el delimitador? – ajreal

+0

no, la cuestión es que quiero obtener todos los caracteres comunes de s1 y s2 – johannes

+0

diferencia extrema entre la simplicidad de la tarea y la complejidad de la solución en la creación de scripts en shell. ¡muy agradable! –

Respuesta

9

Mi solución a continuación utiliza fold para romper la cadena en un solo personaje por línea, sort para ordenar las listas, comm comparar las dos cadenas y finalmente tr eliminar los caracteres de nueva línea

comm -12 <(fold -w1 <<< $s1 | sort -u) <(fold -w1 <<< $s2 | sort -u) | tr -d '\n' 

Alternativamente, aquí es una solución pura de Bash (que también mantiene el orden de los caracteres). Se repite sobre la primera cadena y comprueba si cada carácter está presente en la segunda cadena.

s="temp_foo_bar" 
t="temp_bar" 
i=0 
while [ $i -ne ${#s} ] 
do 
    c=${s:$i:1} 
    if [[ $result != *$c* && $t == *$c* ]] 
    then 
     result=$result$c 
    fi 
    ((i++)) 
done 
echo $result 

impresiones: temp_bar

+0

Sí, añadiré -u al comando de ordenación también. –

+0

Buen uso del comodín: +1. – jfg956

+0

Su segundo método tiene el vicio de no trabajar con espacios en 't' y' s'. Al menos en su forma actual. También es bastante largo. –

1

debe ser una solución portátil:

s1="my_foo" 
s2="my_bar" 
while [ -n "$s1" -a -n "$s2" ] 
do 
    if [ "${s1:0:1}" = "${s2:0:1}" ] 
    then 
     printf %s "${s1:0:1}" 
    else 
     break 
    fi 
    s1="${s1:1:${#s1}}" 
    s2="${s2:1:${#s2}}" 
done 
+0

Esto solo coincide con los caracteres en el mismo índice en ambas cadenas. Entonces no funcionará si tiene 'my_foo_bar' y' my_bar'. – dogbane

2

Suponiendo que las cadenas no contienen nuevas líneas incrustadas:

s1='my_foo' s2='my_bar' 
intersect=$(
    comm -12 <(
    fold -w1 <<< "$s1" | 
     sort -u 
    ) <(
     fold -w1 <<< "$s2" | 
      sort -u 
     ) | 
      tr -d \\n 
      ) 

printf '%s\n' "$intersect" 

Y otro:

tr -dc "$s2" <<< "$s1" 
+1

Su segunda solución con 'tr' es agradable, pero no elimina los duplicados. – dogbane

+0

@dogbane, buen punto! Debería haber mencionado eso. Para eliminar duplicados, ambos valores deben pasar el 'fold .. | sort ..' filter. –

1
comm="" 
for ((i=0;i<${#s1};i++)) 
do 
    if test ${s1:$i:1} = ${s2:$i:1} 
    then 
    comm=${comm}${s1:$i:1} 
    fi 
done 
1

Una solución utilizando una sola ejecución SED:

echo -e "$s1\n$s2" | sed -e 'N;s/^/\n/;:begin;s/\n\(.\)\(.*\)\n\(.*\)\1\(.*\)/\1\n\2\n\3\4/;t begin;s/\n.\(.*\)\n\(.*\)/\n\1\n\2/;t begin;s/\n\n.*//' 

Como toda comando especialmente críptico, sino que precisa de una explicación en forma de un archivo de secuencia de comandos sed que puede ser ejecutado por echo -e "$s1\n$s2" | sed -f script:

# Read the next line so s1 and s2 are in the pattern space only separated by a \n. 
N 
# Put a \n at the beginning of the pattern space. 
s/^/\n/ 
# During the script execution, the pattern space will contain <result so far>\n<what left of s1>\n<what left of s2>. 
:begin 
# If the 1st char of s1 is found in s2, remove it from s1 and s2, append it to the result and do this again until it fails. 
s/\n\(.\)\(.*\)\n\(.*\)\1\(.*\)/\1\n\2\n\3\4/ 
t begin 
# When previous substitution fails, remove 1st char of s1 and try again to find 1st char of S1 in s2. 
s/\n.\(.*\)\n\(.*\)/\n\1\n\2/ 
t begin 
# When previous substitution fails, s1 is empty so remove the \n and what is left of s2. 
s/\n\n.*// 

Si desea eliminar el duplicado, agregue lo siguiente al final del script:

Edición: Me doy cuenta de que la solución de caparazón puro de dogbane tiene el mismo algoritmo, y es probablemente más eficiente.

2

una entrada tardía, me acabo de encontrar esta página:

echo "$str2" | 
    awk 'BEGIN{FS=""} 
    { n=0; while(n<=NF) { 
    if ($n == substr(test,n,1)) { if(!found[$n]) printf("%c",$n); found[$n]=1;} n++; 
    } print ""}' test="$str1" 

y otro, éste construye una expresión regular de la aceptación (nota: no funciona con caracteres especiales, pero eso no es tan difícil para fijar con anonther sed)

echo "$str1" | 
    grep -E -o ^`echo -n "$str2" | sed 's/\(.\)/(|\1/g'; echo "$str2" | sed 's/./)/g'` 
+0

Buena idea para usar 'awk' pero no funciona usando este ejemplo' awk 'BEGIN {FS = ""} {n = 0; while (n <= NF) {if ($ n == substr (test, n, 1)) {printf ("% c", $ n);} n ++;} print ""} 'test = "/ aa/ba/"<<<"/aa/bb/"'. Muestra '/ aa/b /' en lugar de '/ aa/b'. Intenta corregir tu respuesta. Cheers – olibre

+1

@olibre: informe raro :) lo arregló. –

0

Puesto que cada uno ama perl algunas frases llenas de puntuacion:

perl -e '$a{$_}++ for split "",shift; $b{$_}++ for split "",shift; for (sort keys %a){print if defined $b{$_}}' my_foo not_my_bar

Crea hashes %a y %b desde las cadenas de entrada.
Imprime los caracteres comunes a ambas cadenas.

salidas:

_moy 
Cuestiones relacionadas