Обратный порядок элементов в строке, разделенных точками

14

У меня есть строка ввода, как:

arg1.arg2.arg3.arg4.arg5

Я хочу получить вывод:

arg5.arg4.arg3.arg2.arg1

Это не всегда 5 аргументов, может быть от 2 до 10.

Как я могу сделать это в скрипте bash?

Jens
источник

Ответы:

22
  • Используя комбинацию tr+ tac+paste

    $ tr '.' $'\n' <<< 'arg1.arg2.arg3.arg4.arg5' | tac | paste -s -d '.'
    arg5.arg4.arg3.arg2.arg1
  • Если вы все еще предпочитаете bash, вы можете сделать это следующим образом,

    IFS=. read -ra line <<< "arg1.arg2.arg3.arg4."
    let x=${#line[@]}-1; 
    while [ "$x" -ge 0 ]; do 
          echo -n "${line[$x]}."; 
          let x--; 
    done
  • Используя perl,

    $ echo 'arg1.arg2.arg3.arg4.arg5' | perl -lne 'print join ".", reverse split/\./;'
    arg5.arg4.arg3.arg2.arg1
Рахул
источник
2
Бах, я только собирался опубликовать решение Perl :)
ilkkachu
10

Если вы не возражаете против чуть-чуть python:

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

Пример:

$ python3 -c 's="arg1.arg2.arg3.arg4.arg5"; print(".".join(s.split(".")[::-1]))'
arg5.arg4.arg3.arg2.arg1
  • s.split(".")генерирует список, содержащий .разделенные подстроки, [::-1]переворачивает список и ".".join()соединяет компоненты обратного списка с помощью ..
heemayl
источник
5

Вы можете сделать это с помощью одного sedвызова:

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

он использует буфер удержания, чтобы получить начальную новую строку в пространстве шаблона, и использует его для выполнения перестановок, пока за новой строкой больше не следует никакая точка, в которой он удаляет ведущую точку из пространства шаблона и заменяет новую строку точкой.
С BRE и немного другим регулярным выражением:

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

Если вход состоит из более чем одной строки, и вы хотите изменить порядок в каждой строке:

sed -E 'G;:t;s/(.*)(\.)(.*)(\n)(.*)/\1\4\5\2\3/;tt;s/(.*)\n(\.)(.*)/\3\2\1/'
don_crissti
источник
3

SED решение:

sed 's/\./\n/g' yourFile | tac | sed ':a; $!{N;ba};s/\n/./g'
FarazX
источник
3

С zsh:

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

Чтобы сохранить пустые элементы:

$ string=arg1.arg2.arg3.arg4..arg5.
$ printf '%s\n' ${(j:.:)"${(@Oas:.:)string}"}
.arg5..arg4.arg3.arg2.arg1
  • s:.:: разделить на .
  • Oa: Сортировать массив в обратном rray Indice о rder
  • j:.:: Присоединиться на ..
  • @: do- "$@"like расширение, когда в двойных кавычках, поэтому расширение не подвергается пустому удалению.

POSIX (или bash) эквивалент может быть что-то вроде:

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

(обратите внимание, что zshshэмуляции) yashи некоторые pdkshоболочки на основе не являются POSIX в этом отношении в том смысле, что они рассматриваются $IFSкак разделитель полей вместо разделителя полей)

Отличие от некоторых других решений, приведенных здесь, заключается в том, что он не обрабатывает символ новой строки (и, в случае zshNUL), в частности, то есть $'ab.cd\nef.gh'обратный $'gh.cd\nef.ab', нет $'cd.ab\ngh.ef'или $'gh.ef.cd.ab'.

Стефан Шазелас
источник
Что не так с IFS="."; set -f; set -- $string$IFS; for i; do rev="$i$IFS$rev"; done; printf '%s\n' "${rev%$IFS}"?
@BinaryZebra Ничего (кроме, может быть, for i; doне POSIX (пока), даже если он поддерживается всеми известными мне оболочками POSIX). Просто это решение является прямым переводом zsh(разделение на массив, обратный массив, объединение элементов массива), но с использованием операторов только POSIX. (Я изменил , string=$string$IFS; set -- $stringчтобы , set -- $string$IFSкак кажется, работают последовательно через оболочки, спасибо).
Стефан Шазелас
2

Я вижу, что Рахул уже опубликовал собственное решение для bash (среди прочего), которое является более короткой версией первого, который я придумал:

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

но я не мог на этом остановиться и почувствовал необходимость написать рекурсивное решение, ориентированное на bash:

function reversedots2() {
  if [[ $1 =~ ^([^.]*)\.(.*)$ ]]
  then
    printf %s $(reversedots2 "${BASH_REMATCH[2]}") . "${BASH_REMATCH[1]}"
  else
    printf %s "$1"
  fi
}
Джефф Шаллер
источник
2

Не видел awkответа, поэтому вот один:

 echo 'arg1.arg2.arg3' | awk -F. '{for (i=NF; i>0; --i) printf "%s%s", (i<NF ? "." : ""), $i; printf "\n"}'
Пол Эванс
источник
1

Pure Bash, требует завершающего периода на входе:

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

Используйте, printf %s "$g"если любой из пунктирных элементов может начинаться с дефиса.

Эрик
источник