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

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

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

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 

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.


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

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 

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"

0

Nieeleganckie narzędzia oprogramowania POSIX one-liner:

echo `echo $VAR | tr ' ' '\n' | sort -nr`
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.