Odwracanie zawartości zmiennej słowami

13

Więc jeśli mam zmienną

VAR='10 20 30 40 50 60 70 80 90 100'

i powtórz to

echo "$VAR"
10 20 30 40 50 60 70 80 90 100

Jednak w dalszej części skryptu muszę odwrócić kolejność tej zmiennej, aby wyświetlała się jako coś podobnego

echo "$VAR" | <code to reverse it>
100 90 80 70 60 50 40 30 20 10

Próbowałem użyć rev i dosłownie odwróciło wszystko, więc wyszło jak

echo "$VAR" | rev
001 09 08 07 06 05 04 03 02 01
Alistair Hardy
źródło
2
Przydatne sugestie znajdziesz w odpowiedziach na podobne pytanie: Jak odczytać adres IP wstecz? (wystarczy dostosować separatory przestrzeni zamiast kropek)
steeldriver

Odpowiedzi:

21

W systemach GNU odwrotnością cat jest tac:

$ tac -s" " <<< "$VAR "            # Please note the added final space.
100 90 80 70 60 50 40 30 20 10
Ipor Sircer
źródło
Twoje zdrowie! To załatwiło sprawę. Wciąż muszę się wiele nauczyć
Alistair Hardy
@JhonKugelman Bez echa wyjście ma dodatkową echo $'100 \n90 80 70 60 50 40 30 20 10'
nową linię
W bieżącej tac -s" " <<< "$VAR "wersji nadal wstawia wiodącą pustą linię, dodaje spację końcową i pomija znak nowej linii (jak gdyby printf '\n%s ' "$reversed_VAR"zamiast oczekiwanej printf '%s\n' "$reversed_VAR")
Stéphane Chazelas
@don_crissti Oryginalna odpowiedź: echo $(tac -s" " <<< $VAR)nie ma końcowego spacji, ponieważ $(…)usuwa końcowe znaki i spacje, jeśli IFS je ma. Moje rozwiązanie zostanie wkrótce poprawione, dzięki.
sorontar
6

W przypadku krótkiej listy i zmiennej zmienna sama powłoka jest najszybszym rozwiązaniem:

var="10 20 30 40 50 60 70 80 90 100"
set -- $var; unset a 
for ((i=$#;i>0;i--)); do
    printf '%s%s' "${a:+ }" "${!i}"
    a=1
done
echo

Wynik:

100 90 80 70 60 50 40 30 20 10 

Uwaga: operacja rozłam w set -- $varbezpieczny dla dokładnego ciąg używany tutaj, ale nie będzie tak, jeśli ciąg zawiera znaki wieloznaczne ( *, ?lub []). W set -frazie potrzeby można tego uniknąć przed podziałem. Ta sama uwaga dotyczy także następujących rozwiązań (z wyjątkiem przypadków, gdy zaznaczono inaczej).

Lub, jeśli chcesz ustawić inną zmienną z odwrotną listą:

var="10 20 30 40 50 60 70 80 90 100"
set -- $var
for i; do out="$i${out:+ }$out"; done
echo "$out"

Lub używając tylko parametrów pozycyjnych.

var="10 20 30 40 50 60 70 80 90 100"
set -- $var
a=''
for    i
do     set -- "$i${a:+ $@}"
       a=1
done
echo "$1"

Lub używając tylko jednej zmiennej (która może być samą var):
Uwaga: Na to rozwiązanie nie ma wpływu globing ani IFS.

var="10 20 30 40 50 60 70 80 90 100"

a=" $var"
while [[ $a ]]; do
    printf '<%s>' "${a##* }"
    var="${a% *}"
done
sorontar
źródło
2

awk na pomoc

$ var='10 20 30 40 50 60 70 80 90 100'

$ echo "$var" | awk '{for(i=NF; i>0; i--) printf i==1 ? $i"\n" : $i" "}'
100 90 80 70 60 50 40 30 20 10


z perl, dzięki uprzejmości Jak odczytać adres IP wstecz? udostępnione przez @steeldriver

$ echo "$var" | perl -lane '$,=" "; print reverse @F'
100 90 80 70 60 50 40 30 20 10


Lub bashsam z siebie poprzez konwersję łańcucha na tablicę

$ arr=($var)    
$ for ((i=${#arr[@]}-1; i>=0; i--)); do printf "${arr[i]} "; done; echo
100 90 80 70 60 50 40 30 20 10 
Sundeep
źródło
2

Możesz użyć funkcji bash, takich jak tablice, aby zrobić to całkowicie w trakcie:

#!/bin/bash
VAR="10 20 30 40 50 60 70 80 90 100"
a=($VAR); rev_a=()
for ((i=${#a[@]}-1;i>=0;i--)); do rev_a+=( ${a[$i]} ); done; 
RVAR=${rev_a[*]}; 

Powinno to być najszybszym sposobem, aby to zrobić, pod warunkiem, że $ VAR nie jest bardzo duży.

PSkocik
źródło
Nie dotyczy to jednak zmiennych, których wartość zawiera znaki wieloznaczne (jak VAR='+ * ?'). Użyj set -o nogloblub, set -faby to naprawić. Mówiąc bardziej ogólnie, warto wziąć pod uwagę wartość $IFSi stan globowania podczas korzystania z operatora split + glob. Nie, że użycie tego operatora nie ma większego sensu rev_a+=( ${a[$i]} ), rev_a+=( "${a[$i]}" )byłoby o wiele bardziej sensowne.
Stéphane Chazelas
1
printf "%s\n" $var | tac | paste -sd' '
100 90 80 70 60 50 40 30 20 10
Costas
źródło
1

Użyj tutaj trochę magii Python:

bash-4.3$ VAR='10 20 30 40 50 60 70 80 90 100'
bash-4.3$ python -c 'import sys;print " ".join(sys.argv[1].split()[::-1])'  "$VAR" 
100 90 80 70 60 50 40 30 20 10

Sposób, w jaki to działa, jest dość prosty:

  • nazywamy się "$VAR"paramenterem drugiego wiersza poleceń sys.argv[1](pierwszy parametr z -copcją jest zawsze tym -csamym - samym. Zwróć uwagę na cytowanie, "$VAR"aby zapobiec podziałowi samej zmiennej na pojedyncze słowa (co robi to przez powłokę, a nie python)
  • Następnie używamy .split()metody, aby podzielić ją na listę ciągów
  • [::-1]część jest tylko rozszerzoną składnią plastra, aby uzyskać odwrotność listy
  • na koniec łączymy wszystko z powrotem w jeden ciąg " ".join()i drukujemy

Ale ci, którzy nie są wielkimi fanami Pythona, moglibyśmy AWKzrobić w zasadzie to samo: podzielić i wypisać tablicę na odwrót:

 echo "$VAR" | awk '{split($0,a);x=length(a);while(x>-1){printf "%s ",a[x];x--};print ""}'
100 90 80 70 60 50 40 30 20 10 
Sergiy Kolodyazhnyy
źródło
1
lub nieco czystszepython3 -c 'import sys;print(*reversed(sys.argv[1:]))' $VAR
iruvar
@iruvar, nie jest czystszy , który wywołałby operator split + glob powłoki bez upewnienia się, że $IFSzawiera odpowiednie znaki i bez wyłączenia części glob.
Stéphane Chazelas,
@ StéphaneChazelas, prawda - część python jest jeszcze czystsza (IMHO). Wracając do "$VAR"wydajnościpython3 -c 'import sys;print(*reversed(sys.argv[1].split()))' "$VAR"
iruvar,
1

zsh:

print -r -- ${(Oa)=VAR}

$=VARdzieli $VARsię $IFS. (Oa)porządkuje wynikową listę w odwrotnej kolejności tablicowej. print -r --(jak w ksh), to samo co echo -E -( zshspecyficzne) to niezawodne wersjeecho : wypisuje swoje argumenty, gdy jest oddzielone spacją, zakończone znakiem nowej linii.

Jeśli chcesz podzielić tylko na spację, a nie na to, co $IFSzawiera (spacja, tabulator, nowa linia, domyślnie nul), albo przypisz spację $IFS, albo użyj jawnego podziału, takiego jak:

print -r -- ${(Oas: :)VAR}

Aby sortować w odwrotnej kolejności numerycznej:

$ VAR='50 10 20 90 100 30 60 40 70 80'
$ print -r -- ${(nOn)=VAR}
100 90 80 70 60 50 40 30 20 10

POSIXly (tak też by działał bash):

Z wbudowaną powłoką (z wyjątkiem printfniektórych powłok) tylko mechanizmy (lepsze dla zmiennych o krótkiej wartości):

unset -v IFS # restore IFS to its default value of spc, tab, nl
set -o noglob # disable glob
set -- $VAR # use the split+glob operator to assign the words to $1, $2...

reversed_VAR= sep=
for i do
  reversed_VAR=$i$sep$reversed_VAR
  sep=' '
done
printf '%s\n' "$reversed_VAR"

Z awk(lepiej dla dużych zmiennych, szczególnie z bash, ale do granicy wielkości argumentów (lub pojedynczego argumentu)):

 awk '
   BEGIN {
     n = split(ARGV[1], a);
     while (n) {printf "%s", sep a[n--]; sep = " "}
     print ""
   }' "$VAR"
Stéphane Chazelas
źródło
0

Nieeleganckie narzędzia oprogramowania POSIX one-liner:

echo `echo $VAR | tr ' ' '\n' | sort -nr`
agc
źródło