Orden inverso de elementos delimitados por puntos en una cadena


14

Tengo una cadena de entrada como:

arg1.arg2.arg3.arg4.arg5

La salida que quiero es:

arg5.arg4.arg3.arg2.arg1

No siempre son 5 argumentos, podría ser de 2 a 10.

¿Cómo puedo hacer esto en un script bash?

Respuestas:


22
  • Usando la combinación de tr+ tac+paste

    $ tr '.' $'\n' <<< 'arg1.arg2.arg3.arg4.arg5' | tac | paste -s -d '.'
    arg5.arg4.arg3.arg2.arg1
  • Si aún prefieres bash, puedes hacerlo de esta manera,

    IFS=. read -ra line <<< "arg1.arg2.arg3.arg4."
    let x=${#line[@]}-1; 
    while [ "$x" -ge 0 ]; do 
          echo -n "${line[$x]}."; 
          let x--; 
    done
  • Utilizando perl,

    $ echo 'arg1.arg2.arg3.arg4.arg5' | perl -lne 'print join ".", reverse split/\./;'
    arg5.arg4.arg3.arg2.arg1

2
Bah, solo iba a publicar la solución Perl :)
ilkkachu

10

Si no le importa un poquito de python:

".".join(s.split(".")[::-1])

Ejemplo:

$ python3 -c 's="arg1.arg2.arg3.arg4.arg5"; print(".".join(s.split(".")[::-1]))'
arg5.arg4.arg3.arg2.arg1
  • s.split(".")genera una lista que contiene .subcadenas separadas, [::-1]invierte la lista y ".".join()une los componentes de la lista invertida con ..

5

Puedes hacerlo con una sola sedinvocación:

sed -E 'H;g;:t;s/(.*)(\n)(.*)(\.)(.*)/\1\4\5\2\3/;tt;s/\.(.*)\n/\1./'

esto usa el búfer de retención para obtener una nueva línea inicial en el espacio del patrón y lo usa para realizar permutaciones hasta que la nueva línea ya no esté seguida de ningún punto en el punto en que elimine el punto inicial del espacio del patrón y reemplace la nueva línea con un punto.
Con BRE y una expresión regular ligeramente diferente:

sed 'H
g
:t
s/\(.*\)\(\n\)\(.*\)\(\.\)\(.*\)/\1\4\5\2\3/
tt
s/\(\.\)\(.*\)\n/\2\1/'

Si la entrada consta de más de una línea y desea invertir el orden en cada línea:

sed -E 'G;:t;s/(.*)(\.)(.*)(\n)(.*)/\1\4\5\2\3/;tt;s/(.*)\n(\.)(.*)/\3\2\1/'

3

Solución SED:

sed 's/\./\n/g' yourFile | tac | sed ':a; $!{N;ba};s/\n/./g'

3

Con zsh:

$ string=arg1.arg2.arg3.arg4.arg5
$ printf '%s\n' ${(j:.:)${(Oas:.:)string}}
arg5.arg4.arg3.arg2.arg1

Para preservar elementos vacíos:

$ string=arg1.arg2.arg3.arg4..arg5.
$ printf '%s\n' ${(j:.:)"${(@Oas:.:)string}"}
.arg5..arg4.arg3.arg2.arg1
  • s:.:: dividido en .
  • Oa: Array especie a la inversa un RRay indice O rden
  • j:.:: únete ..
  • @: hacer una "$@"expansión similar a comillas dobles para que la expansión no se elimine en vacío.

El POSIX (o bash) equivalente podría ser algo como:

string=arg1.arg2.arg3.arg4..arg5.

IFS=.; set -f
set -- $string$IFS # split string into "$@" array
unset pass
for i do # reverse "$@" array
  set -- "$i" ${pass+"$@"}
  pass=1
done
printf '%s\n' "$*" # "$*" to join the array elements with the first
                   # character of $IFS

(tenga zshen cuenta que (en la shemulación), yashy algunos pdkshshells basados ​​no son POSIX en ese sentido, ya que se tratan $IFScomo un separador de campo en lugar de un delimitador de campo)

Donde difiere de algunas de las otras soluciones dadas aquí es que no trata el carácter de nueva línea (y en el caso del zshNUL tampoco) especialmente, eso se $'ab.cd\nef.gh'revertiría a $'gh.cd\nef.ab', no $'cd.ab\ngh.ef'o $'gh.ef.cd.ab'.


¿Qué tiene de malo IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"?

@BinaryZebra Nothing (aparte quizás del for i; doque no es POSIX (todavía) incluso si es compatible con todos los shells POSIX que conozco). Es solo que esa solución es una traducción directa de la zshmisma (dividida en elementos de matriz, matriz inversa, elementos de matriz de combinación) pero utilizando operadores solo POSIX. (He cambiado el string=$string$IFS; set -- $stringa set -- $string$IFSya que parece funcionar de manera consistente en todos los shells, gracias).
Stéphane Chazelas

2

Veo que Rahul ya publicó una solución bash nativa (entre otras), que es una versión más corta de la primera que se me ocurrió:

function reversedots1() (
  IFS=. read -a array <<<"$1"
  new=${array[-1]}
  for((i=${#array[*]} - 2; i >= 0; i--))
  do
    new=${new}.${array[i]}
  done
  printf '%s\n' "$new"
)

pero no pude parar allí, y sentí la necesidad de escribir una solución recursiva centrada en bash:

function reversedots2() {
  if [[ $1 =~ ^([^.]*)\.(.*)$ ]]
  then
    printf %s $(reversedots2 "${BASH_REMATCH[2]}") . "${BASH_REMATCH[1]}"
  else
    printf %s "$1"
  fi
}

2

No vi una awkrespuesta, así que aquí hay una:

 echo 'arg1.arg2.arg3' | awk -F. '{for (i=NF; i>0; --i) printf "%s%s", (i<NF ? "." : ""), $i; printf "\n"}'

1

Pure Bash, requiere un período final en la entrada:

echo 'foo.bar.baz.' | ( while read -d . f;do g="$f${g+.}$g" ;done;echo "$g" )

Use printf %s "$g"si alguno de los elementos punteados podría comenzar con un guión.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.