Ordre inverse des éléments délimités par des points dans une chaîne

14

J'ai une chaîne d'entrée comme:

arg1.arg2.arg3.arg4.arg5

La sortie que je veux est:

arg5.arg4.arg3.arg2.arg1

Ce n'est pas toujours 5 arguments, pourrait être de 2 à 10.

Comment puis-je faire cela dans un script bash?

Jens
la source

Réponses:

22
  • Utilisation d'une combinaison de tr+ tac+paste

    $ tr '.' $'\n' <<< 'arg1.arg2.arg3.arg4.arg5' | tac | paste -s -d '.'
    arg5.arg4.arg3.arg2.arg1
  • Si vous préférez toujours bash, vous pouvez le faire de cette façon,

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

    $ echo 'arg1.arg2.arg3.arg4.arg5' | perl -lne 'print join ".", reverse split/\./;'
    arg5.arg4.arg3.arg2.arg1
Rahul
la source
2
Bah, j'allais juste poster la solution Perl :)
ilkkachu
10

Si cela ne vous dérange pas un petit peu de python:

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

Exemple:

$ python3 -c 's="arg1.arg2.arg3.arg4.arg5"; print(".".join(s.split(".")[::-1]))'
arg5.arg4.arg3.arg2.arg1
  • s.split(".")génère une liste contenant .des sous-chaînes séparées, [::-1]inverse la liste et ".".join()joint les composants de la liste inversée avec ..
heemayl
la source
5

Vous pouvez le faire avec une seule sedinvocation:

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

cela utilise le tampon de maintien pour obtenir une nouvelle ligne de début dans l'espace de motif et l'utilise pour faire des permutations jusqu'à ce que la nouvelle ligne ne soit plus suivie par aucun point auquel point il supprime le point de tête de l'espace de motif et remplace la nouvelle ligne par un point.
Avec BRE et un regex légèrement différent:

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

Si l'entrée se compose de plusieurs lignes et que vous souhaitez inverser l'ordre sur chaque ligne:

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

Solution SED:

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

Avec zsh:

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

Pour conserver les éléments vides:

$ string=arg1.arg2.arg3.arg4..arg5.
$ printf '%s\n' ${(j:.:)"${(@Oas:.:)string}"}
.arg5..arg4.arg3.arg2.arg1
  • s:.:: divisé sur .
  • Oa: Tableau de tri en sens inverse un rray indice o rder
  • j:.:: rejoignez-nous ..
  • @: "$@"extension à la manière des guillemets doubles pour que l'expansion ne soit pas supprimée à vide.

L' bashéquivalent POSIX (ou ) pourrait ressembler à:

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

(notez que zsh(en shémulation), yashet certainspdksh shells basés sur ne sont pas POSIX à cet égard en ce qu'ils traitent $IFScomme un séparateur de champ au lieu d'un délimiteur de champ)

Là où il diffère de certaines des autres solutions données ici, c'est qu'il ne traite pas zshspécialement le caractère de nouvelle ligne (et dans le cas de , le NUL), qui $'ab.cd\nef.gh'serait inversé en $'gh.cd\nef.ab', pas $'cd.ab\ngh.ef'ou $'gh.ef.cd.ab'.

Stéphane Chazelas
la source
Qu'est-ce qui ne va pas IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"?
@BinaryZebra Rien (à part peut-être celui for i; doqui n'est pas (encore) POSIX même s'il est supporté par tous les shells POSIX que je connais). C'est juste que cette solution est une traduction directe de zshcelle (divisée en tableau, tableau inversé, joindre des éléments de tableau) mais en utilisant des opérateurs POSIX uniquement. (J'ai changé le string=$string$IFS; set -- $stringen set -- $string$IFScar cela semble fonctionner de manière cohérente entre les shells, merci).
Stéphane Chazelas
2

Je vois que Rahul a déjà posté une solution bash native (entre autres), qui est une version plus courte de la première que j'ai inventée:

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"
)

mais je n'ai pas pu m'arrêter là, et j'ai ressenti le besoin d'écrire une solution récursive centrée sur bash:

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

Je n'ai pas vu de awkréponse, alors en voici une:

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

Pure Bash, nécessite une période de fin en entrée:

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

À utiliser printf %s "$g"si l'un des éléments en pointillé peut commencer par un trait d'union.

Eric
la source