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?

Jens
fuente

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
Rahul
fuente
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 ..
heemayl
fuente
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/'
don_crissti
fuente
3

Solución SED:

sed 's/\./\n/g' yourFile | tac | sed ':a; $!{N;ba};s/\n/./g'
FarazX
fuente
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'.

Stéphane Chazelas
fuente
¿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
}
Jeff Schaller
fuente
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"}'
Paul Evans
fuente
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.

Eric
fuente