Bash'de bir dizi nasıl sıralanır?


140

Bash, örneğin bir dizi var:

array=(a c b f 3 5)

Diziyi sıralamam gerekiyor. İçeriği sıralı bir şekilde görüntülemekle kalmayıp, sıralı öğelerle yeni bir dizi elde etmek için. Yeni sıralanmış dizi tamamen yeni veya eski dizi olabilir.

Yanıtlar:


208

Gerçekten bu kadar koda ihtiyacınız yok:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

Elemanlardaki boşlukları destekler (yeni satır olmadığı sürece) ve Bash 3.x'te çalışır.

Örneğin:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

Not: @sorontar olan işaret elemanları gibi genel arama karakterleri içeriyorsa bakımı gerekli olduğu *ya da ?:

Sort = ($ (...)) bölümü "split and glob" operatörünü kullanıyor. Glob'u kapatmalısınız: set -fveya set -o noglobveya shopt -op noglobveya dizinin bir öğesi *bir dosya listesine genişletilecektir.

Ne oluyor:

Sonuç, bu sırada gerçekleşen altı şeyin doruk noktasıdır:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

İlk önce IFS=$'\n'

Bu, operasyonumuzun 2 ve 5'in sonuçlarını aşağıdaki şekilde etkileyen önemli bir parçasıdır:

Verilen:

  • "${array[*]}" ilk karakteri ile sınırlanan her öğeye genişler IFS
  • sorted=() her karakterine bölünerek elemanlar oluşturur IFS

IFS=$'\n' öğeleri yeni bir satır kullanarak genişletilecek şekilde ayarlar , sınırlayıcı olarak ve daha sonra her satır bir öğe olacak şekilde oluşturulacak şekilde . (örneğin, yeni bir satıra bölme.)

Yeni bir satırla sınırlamak önemlidir, çünkü bu şekilde sortçalışır (satır başına sıralama). Yalnızca yeni bir satıra bölmek o kadar önemli değildir, ancak boşluk veya sekme içeren öğeleri korumak gerekir.

Varsayılan değeri IFSolan bir boşluk , bir sekme ardından yeni bir satır ve bizim operasyon için elverişsiz olacaktır.

Sonra, sort <<<"${array[*]}"bölüm

<<<, burada dizeler olarak adlandırılır , "${array[*]}"yukarıda açıklandığı gibi genişlemesini alır ve standart girdiyesort .

Örneğimizle, sortşu dizeyle beslenir:

a c
b
f
3 5

sort Çeşitlerinden beri üretir:

3 5
a c
b
f

Sonra, sorted=($(...))bölüm

$(...)Denilen kısmı, komut ikamesi , içeriğini (neden sort <<<"${array[*]}sonuçlanan çekerken, normal bir komut olarak çalıştırmak için) standart çıktıyı nereye kadar gider literal olarak $(...)oldu.

Örneğimizde, bu sadece yazmaya benzer bir şey üretir:

sorted=(3 5
a c
b
f
)

sorted daha sonra bu değişmez değeri her yeni satıra bölerek oluşturulan bir dizi haline gelir.

Sonunda, unset IFS

Bu, değerini IFSvarsayılan değerine sıfırlar ve sadece iyi bir uygulamadır.

Daha IFSsonra senaryomuzda güvenilen hiçbir şeyle ilgili sorun yaratmamamızı sağlamaktır . (Aksi takdirde, bazı şeyleri değiştirdiğimizi hatırlamamız gerekir - karmaşık komut dosyaları için pratik olmayan bir şey.)


2
@xxor olmadan, IFSöğelerinizde boşluklar varsa öğelerinizi küçük parçalara böler. ÖrneğinIFS=$'\n' atlanmış ile deneyin ve görün!
antak

3
Çok hoş. Ortalama bash kullanıcısı için bu çözümün nasıl çalıştığını açıklayabilir misiniz?
u32004

2
Şimdi, içinde IFSsadece bir tür boşluk varsa, öğelerinizi küçük parçalara ayırır. İyi; mükemmel değil :-)
Sınırlı Kefaret

7
unset IFSgerekli? IFS=Bir komuta geçmenin , sadece bu komuta geçişi kapsadığını , daha sonra otomatik olarak önceki değerine döndüğünü düşündüm .
Mark H

10
@MarkH Gerekli çünkü sorted=()bir komut değil, ikinci bir değişken ataması.
antak

35

Orijinal yanıt:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

çıktı:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

Bu sürümün özel karakterler veya boşluk içeren değerlerle ( yeni satırlar hariç ) başa çıktığını unutmayın

Not readarray, bash 4 ve üstü sürümlerde desteklenir.


Düzenle @Dimitre tarafından yapılan öneriye dayanarak şu şekilde güncellemiştim:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

hatta doğru yerleştirilmiş yeni satır karakterleri ile sıralama öğeleri anlamak yararına sahiptir . Ne yazık ki, @ruakh tarafından doğru bir şekilde işaret edildiği gibi, bu, sonucun doğrureadarray olacağı anlamına gelmedi , çünkü satır ayırıcılar olarak normal satır satırları yerine kullanma seçeneği yoktur .readarrayNUL


5
Güzel, ayrıca bash'ın 4. versiyonundan beri readarray'ın da mevcut olduğuna dikkat edilmelidir. Biraz kısaltılabilir:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov

1
@Dimitre: Önerinizi aldım ve beyaz alan işlemeyi herhangi bir şeyle (dahili olarak nullchar-ayırıcılar kullanarak) çalışacak şekilde düzelttim. Şerefe
Eylül'de se

1
Evet, sort -zyararlı bir gelişme, -zseçenek bir GNU sıralama uzantısı olduğunu varsayalım .
Dimitre Radoulov

2
Katıştırılmış yeni satırlarla başa çıkmak istiyorsanız, kendi yeniden dizginizi yuvarlayabilirsiniz. Örneğin: sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). Bu, bash v4 yerine bash v3 kullandığınız için de geçerlidir, çünkü bash v3'te readarray kullanılamaz.
Bob Bell

1
@ user1527227 Bu işlem ikamesi< ile birlikte girdi yeniden yönlendirmesi ( ) . Ya da sezgisel olarak koymak: çünkü bir dosya değil. <(...)(printf "bla")
sehe

33

İşte saf bir Bash quicksort uygulaması:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
   local pivot i smaller=() larger=()
   qsort_ret=()
   (($#==0)) && return 0
   pivot=$1
   shift
   for i; do
      if (( i < pivot )); then
         smaller+=( "$i" )
      else
         larger+=( "$i" )
      fi
   done
   qsort "${smaller[@]}"
   smaller=( "${qsort_ret[@]}" )
   qsort "${larger[@]}"
   larger=( "${qsort_ret[@]}" )
   qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}

Örneğin,

$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'

Bu uygulama özyinelemeli… yani yinelemeli bir hızlı sıralama:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
   (($#==0)) && return 0
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

Her iki durumda da, kullandığınız sırayı değiştirebilirsiniz: Dize karşılaştırmaları kullandım, ancak aritmetik karşılaştırmaları kullanabilir, wrt dosyası değiştirme zamanını karşılaştırabilir, vb. daha genel hale getirebilir ve test işlevinin kullanıldığı ilk argümanı kullanmasını sağlayabilirsiniz, ör.

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
   (($#<=1)) && return 0
   local compare_fun=$1
   shift
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

Sonra bu karşılaştırma fonksiyonuna sahip olabilirsiniz:

compare_mtime() { [[ $1 -nt $2 ]]; }

ve kullan:

$ qsort compare_mtime *
$ declare -p qsort_ret

geçerli klasördeki dosyaların değişiklik zamanına göre sıralanması (önce en yenisi).

NOT. Bu işlevler saf Bash! harici yardımcı programlar ve alt kabuklar yok! komik semboller (boşluklar, yeni satır karakterleri, glob karakterleri vb.)


1
Giriş öğeleri ve sıralama kriterleri açısından büyük esneklik sunan etkileyici Bashing için şeref. Sunulan sıralama seçenekleriyle satır tabanlı sıralama sortyeterliyse, bir sort+ read -açözümü, örneğin 20 öğeden başlayarak daha hızlı ve daha fazla uğraştığınız unsurları giderek ve önemli ölçüde daha hızlı olacaktır. Örneğin, 2012'nin sonlarında bir Fusion Drive ile OSX 10.11.1 çalıştıran iMac'imde: 100 elemanlı dizi: ca. 0.03s saniye. ( qsort()) ve ca. 0.005 saniye. ( sort+ read -a); 1000 elemanlık dizi: ca. 0.375 saniye. ( qsort()) ve ca. 0.014 saniye ( sort+ read -a).
mklement0

Güzel. Üniversite günlerinden hızlı sıralama hatırlıyorum ama aynı zamanda kabarcık sıralama araştırır. Benim sıralama ihtiyaçları için anahtar ve ardından bir veri öğesi (daha sonra genişletmek olabilir) oluşturan birinci ve ikinci öğeleri var. Kodunuz, anahtar öğe sayısı (parm1) ve veri öğesi sayısı (parm2) ile geliştirilebilir. OP için parametreler 1 ve 0 olacaktır. Benim için parametreler 2 ve 1 olacaktır. Herhangi bir açıdan cevabınız en çok söz verir.
WinEunuuchs2Unix

1
Bulunan yayınlanmamış dize tamsayılarının bir veri kümesiyle if [ "$i" -lt "$pivot" ]; thengerekliydi, aksi takdirde çözülen "2" <"10" true değerini döndürdü. Bunun POSIX ve Sözlükbilimsel olduğuna inanıyorum; veya belki Inline Link .
Sayfa2PagePro

27

Dizi öğelerinde özel kabuk karakterlerini işlemeniz gerekmiyorsa:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

Bash ile zaten harici bir sıralama programına ihtiyacınız olacak.

Zsh ile harici programlara gerek yoktur ve özel kabuk karakterleri kolayca ele alınır:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

Ksh vardır set -ssıralamak için ASCIIbetically .


Çok güzel arka plan bilgisi. Neredeyse ksh set -s bayrağını nasıl kullanacağına dair bir demo isteyecektim ... ama sonra tekrar, soru
bash'da

Bu, çoğu KornShell uygulamasında (örneğin ksh88 ve pdksh ) çalışmalıdır: set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" Ve elbette, set komutu, varsa, geçerli konum parametrelerini sıfırlar.
Dimitre Radoulov

Sen gerçek bir kabuk bilgisi çeşmesin. Eminim fotoğraf belleğine falan sahip olmalısınız, çünkü bu tür ince farklılıklar insan türünün diğer üyelerinin
çoğundan kaçınır

10

tl; dr :

Diziyi sıralayın a_inve sonucu saklayın a_out(öğelerin gömülü yeni satırları olmamalıdır [1] ):

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Bash v3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Antak'ın çözümüne göre avantajları :

  • Yanlışlıkla globbing (dizi öğelerinin dosya adı kalıpları olarak yanlışlıkla yorumlanması) konusunda endişelenmenize gerek yoktur, bu nedenle globbing'i devre dışı bırakmak ( set -fve set +fdaha sonra geri yüklemek için) için fazladan komut gerekmez .

  • Sen sıfırlama hakkında endişe gerekmez IFSile unset IFS. [2]


İsteğe bağlı okuma: açıklama ve örnek kod

Yukarıdaki, rastgele tek satırlı öğelerle ve sözcüksel veya sayısal sıralama (isteğe bağlı olarak alana göre) ile çalışansort bir çözüm için Bash kodunu harici yardımcı programla birleştirir :

  • Performans : Yaklaşık 20 veya daha fazla element için , bu saf Bash çözümünden daha hızlı olacaktır - yaklaşık 100 elementin ötesine geçtiğinizde önemli ölçüde ve giderek daha fazla.
    (Tam eşikler, giriş, makine ve platformunuza bağlı olacaktır.)

    • Hızlı olmasının nedeni Bash döngülerinden kaçınmasıdır .
  • printf '%s\n' "${a_in[@]}" | sort sıralama yapar (sözlük olarak varsayılan olarak - sortPOSIX spesifikasyonuna bakın ):

    • "${a_in[@]}"dizinin öğelerine, içerdikleri her ne olursa olsun (boşluk dahil) bağımsız bir şekilde bağımsıza_in olarak genişler .

    • printf '%s\n' sonra her bir argümanı - yani her dizi elemanını - kendi satırına olduğu gibi yazdırır.

  • Not bir kullanımını işlemi ikame ( <(...)) bir giriş olarak sıralanmış çıkışı sağlamak için read/ ' readarray(stdin, yeniden yönlendirme ile <, çünkü) read/ readarrayçalışmalıdır akım kabuk (a çalıştırmak gerekir alt kabuğa çıkış değişkeni için sırayla) a_outgörünür olmasını geçerli kabuğa (değişkenin kodun geri kalanında tanımlanmış olarak kalması için).

  • Okuma sort çıktısını dizi değişkenine çevirir :

    • Bash v4 +: readarray -t a_outayrı ayrı satırları okursort her bir öğenin ( ) a_outsonuna iz bırakmadan , dizi değişkeninin öğelerine .\n-t

    • Bash v3: readarraymevcut değil, bu yüzden readkullanılmalıdır: dizi ( ) değişkenine okumayı, tüm girdiyi satırlar ( ) arasında okumayı , ancak yeni satır ((LF) ile satır öğelerine ayırmayı
      IFS=$'\n' read -d '' -r -a a_outsöyler . ), ANSI C alıntılı bir dizedir ). ( , neredeyse her zaman birlikte kullanılması gereken bir seçenekread-aa_out-d ''IFS=$'\n'$'\n'
      -rread , \karakterlerin beklenmedik şekilde işlenmesini devre dışı bırakır .)

Açıklamalı örnek kod:

#!/usr/bin/env bash

# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )

# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"

sortSeçeneklerin kullanılmamasından dolayı , bu, sözcüksel sıralama sağlar (rakamlar harflerden önce sıralanır ve basamak dizileri sayı olarak değil, sözcük olarak ele alınır):

*
10
5
a c
b
f

Eğer isteseydik sayısal 1 alana göre sıralama, kullanmak istiyorum sort -k1,1nyerine sadece sorthangi verimleri (bir çeşit sayılar önce olmayan numaralar ve sayılar doğru sıralama),:

*
a c
b
f
5
10

[1] gömülü yeni satır ile etmek için sap elemanları, aşağıdaki varyantı ile (Bash V4 +, kullanımı GNU'da sort )
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
Michał Górny'nin yardımcı cevabı Bash v3 çözümüne sahip.

, [2] IFS bir Bash v3 varyantı yer değiştirmek olan komuta kapsama .
Buna karşılık, ne şu IFS=$'\n' antak yanıtında bir olan atama bu durumda bir komutla, yerine IFSdeğişimdir küresel .


8

Münih'ten Frankfurt'a giden 3 saatlik tren gezisinde (Oktoberfest yarın başlayacağı için ulaşmakta sorun yaşadım) ilk görevimi düşünüyordum. Genel bir dizi kullanmak, genel bir sıralama işlevi için çok daha iyi bir fikirdir. Aşağıdaki işlev, arbiter dizeleri (satırsonları, boşluklar vb.) İşler:

declare BSORT=()
function bubble_sort()
{   #
    # @param [ARGUMENTS]...
    #
    # Sort all positional arguments and store them in global array BSORT.
    # Without arguments sort this array. Return the number of iterations made.
    #
    # Bubble sorting lets the heaviest element sink to the bottom.
    #
    (($# > 0)) && BSORT=("$@")
    local j=0 ubound=$((${#BSORT[*]} - 1))
    while ((ubound > 0))
    do
        local i=0
        while ((i < ubound))
        do
            if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
            then
                local t="${BSORT[$i]}"
                BSORT[$i]="${BSORT[$((i + 1))]}"
                BSORT[$((i + 1))]="$t"
            fi
            ((++i))
        done
        ((++j))
        ((--ubound))
    done
    echo $j
}

bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}

Bu yazdırır:

3 5 a b c z y

Aynı çıktı,

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

Muhtemelen Bash'in dahili olarak akıllı işaretçiler kullandığını unutmayın, bu yüzden takas işlemi ucuz olabilir (buna rağmen şüpheliyim). Bununla birlikte, bubble_sortdaha gelişmiş fonksiyonların merge_sortda kabuk dilinin erişiminde olduğunu gösterir.


5
Kabarcık sıralaması? Vay .. Obama "kabarcık sıralama gitmek için yanlış bir yol olurdu" diyor -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
Görünüşe göre, O-adam akıllı olmak isterken, bunun 50/50 şanslı bir soru olmadığını hissetmemişti. O-guy pozisyonunda bir selefi, ona B-guy'u bir zamanlar çok daha iyi yaptığını söyleyelim (Reynoldsburg, Ohio, Ekim 2000): "Sanırım neye inandığını biliyorsan, soruları cevaplamayı çok daha kolay hale getiriyor Sorunuzu cevaplayamıyorum. " Yani bu B-adamı gerçekten Boolean mantığı hakkında bir şeyler biliyor. O-adam bilmiyor.
Andreas Spindler

BSORT, hangi dizinin sıralanacağına ilişkin bir ad içeren yerel bir dizi haline getirilerek işlev daha kolay taşınabilir hale getirilebilir. yani local -n BSORT="$1"işlevin başlangıcında. Sonra myarraybubble_sort myarray sıralamak için koşabilirsiniz .
johnraff

7

Harici kullanan sortve herhangi bir özel karakterle başa çıkan başka bir çözüm (NUL'lar hariç :)). Bash-3.2 ve GNU veya BSD ile çalışmalıdır sort(ne yazık ki POSIX içermez -z).

local e new_array=()
while IFS= read -r -d '' e; do
    new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)

Önce sondaki giriş yönlendirmesine bakın. printfSıfır sonlandırılmış dizi öğelerini yazmak için yerleşik kullanıyoruz . Alıntı, dizi öğelerinin olduğu gibi iletildiğinden emin printfolur ve kabuk özellikleri, kalan her parametre için biçim dizesinin son bölümünü yeniden kullanmasına neden olur. Yani, şuna benzer:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

Sonra null sonlandırılmış öğe listesi iletilir sort. Bu -zseçenek, boş sonlandırılmış öğeleri okumasına, sıralamasına ve boş sonlandırılmış çıkış vermesine neden olur. Sadece benzersiz öğeleri elde etmeniz gerekiyorsa -u, daha taşınabilir olduğu için geçebilirsiniz uniq -z. LC_ALL=CYereli bağımsız kararlı sıralama düzeni sağlar - komut dosyaları için bazen yararlı. sortYerel ayara saygı duymak istiyorsanız, bunu kaldırın.

<()Yapı olurken boru okuma tanımlayıcıyı alır ve <standart giriş yönlendirmeleriwhile buna döngü. Borunun içindeki standart girişe erişmeniz gerekiyorsa, başka bir tanımlayıcı kullanabilirsiniz - okuyucu için egzersiz :).

Şimdi, başlangıca geri dönelim. readYerleşik Yönlendirilen stdin'den çıkışını okur. Boş ayar yapmak IFS, burada gereksiz olan kelime bölmeyi devre dışı bırakır - sonuç olarak, readsağlanan tek 'değişken' satırını sağlanan tek değişkene okur. -rseçeneği, burada istenmeyen kaçış işlemini de devre dışı bırakır. Son olarak, -d ''çizgi sınırlayıcıyı NUL olarak ayarlar - yani,read sıfır sonlu dizeleri okumayı .

Sonuç olarak, döngü her ardışık sıfır sonlandırılmış dizi öğesi için bir kez yürütülür ve değer depolanır e. Örnek sadece öğeleri başka bir diziye koyar, ancak doğrudan işlemeyi tercih edebilirsiniz :).

Tabii ki, bu aynı hedefe ulaşmanın birçok yolundan sadece biri. Gördüğüm gibi, bash'da tam sıralama algoritması uygulamaktan daha basit ve bazı durumlarda daha hızlı olacak. Yeni satırlar da dahil olmak üzere tüm özel karakterleri işler ve ortak sistemlerin çoğunda çalışmalıdır. En önemlisi, size bash hakkında yeni ve harika bir şey öğretebilir :).


Harika bir çözüm ve çok yararlı bir açıklama, teşekkürler. Bir uzantı: IFS'yi boş olarak ayarlamadan, öndeki boşluk da ortadan kaldırılır - aksi halde kelime bölme yapılmasa bile.
Dirk Herrmann

Yerel değişkeni tanıtmak eve boş IFS ayarlamak yerine REPLY değişkenini kullanın.
Robin A. Meade

2

bunu dene:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

Çıktı:

3
5
bir
b
c
f

Sorun çözüldü.


3
Sorusunu tam olarak cevaplamak için çıktıyı yeni bir diziye koymak için bunu düzenlemeniz gerekir.
Peter Oram

2

Dizideki her öğe için benzersiz bir tamsayı hesaplayabiliyorsanız, şöyle:

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"

Bash her zaman seyrek dizi kullandığından, bu tam sayıları dizi dizinleri olarak kullanabilirsiniz, bu nedenle kullanılmayan dizinler hakkında endişelenmenize gerek yoktur:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • Artıları. Hızlı.
  • Eksileri. Çoğaltılan öğeler birleştirilir ve içerikleri 32 bit benzersiz tamsayılarla eşlemek imkansız olabilir.

İlginç bir teknik, açık bir karşılaştırma / sıralama olmadan max / min değerleri bulmak için bir varyant kullandım. Ancak , uzunluğa bakılmaksızın ağırlıksız ekleme işe yaramaz: "aaaa" dan önce "z" sıralama yapar, bu yüzden bunu yukarıda gösterildiği gibi kelimeler için kullanamazsınız.
mr.spuratic

2

dk. sıralama:

#!/bin/bash
array=(.....)
index_of_element1=0

while (( ${index_of_element1} < ${#array[@]} )); do

    element_1="${array[${index_of_element1}]}"

    index_of_element2=$((index_of_element1 + 1))
    index_of_min=${index_of_element1}

    min_element="${element_1}"

        for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
            min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
            if [[ "${min_element}" == "${element_2}" ]]; then
                index_of_min=${index_of_element2}
            fi
            let index_of_element2++
        done

        array[${index_of_element1}]="${min_element}"
        array[${index_of_min}]="${element_1}"

    let index_of_element1++
done

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

new_array'ın yankı içeriği:

3 5 a b c f

1

Alanların ve yeni satırların olağan sorunu için bir çözüm var:

Orijinal dizideki (gibi olmayan bir karakter kullanarak $'\1'veya $'\4'veya benzeri).

Bu işlev işi yapar:

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }

Bu diziyi sıralar:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

Bu, kaynak dizinin geçici karakter içerdiğinden şikayet edecektir:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

açıklama

  • İki yerel değişken wa(geçici çözüm karakter) ve boş bir IFS ayarladık
  • Sonra (ifs null ile) tüm diziyi test ediyoruz $*.
  • Herhangi bir woraround karakter içermez [[ $* =~ [$wa] ]].
  • Varsa, bir mesaj kaldırın ve bir hata sinyali verin: exit 1
  • Dosya adı genişletmelerinden kaçının: set -f
  • IFS=$'\n'Bir döngü değişkeni xve bir yeni satır var ( nl=$'\n') yeni bir IFS ( ) değeri ayarlayın .
  • Alınan argümanların (girdi dizisi $@) tüm değerlerini yazdırıyoruz .
  • ancak herhangi bir yeni satırı geçici karakterle değiştiriyoruz "${@//$nl/$wa}".
  • sıralanacak değerleri gönderin sort -n.
  • ve sıralanan tüm değerleri konum bağımsız değişkenlerine geri yerleştirin set --.
  • Sonra her bir argümanı birer birer atarız (yeni satırları korumak için).
  • bir döngüde for x
  • yeni bir diziye: sorted+=(…)
  • mevcut satırsonu korumak için tırnak içine alın.
  • geçici çözümü bir yeni satıra geri yükleme "${x//$wa/$nl}".
  • tamam

1

Bu soru birbiriyle yakından ilgili. Ve BTW, işte Bash'te bir birleşim (harici işlemler olmadan):

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"

0

Bash'te harici bir sıralama programına ihtiyacınız olacağından emin değilim.

İşte basit kabarcık sıralama algoritması için benim uygulama.

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

Bu aşağıdakileri basacaktır:

 input: a c b f 3 5
output: 3 5 a b c f

Kabarcık sıralaması O(n^2). Çoğu sıralama algoritmalarının O(n lg(n))son düzine öğeye kadar kullandığını hatırlıyorum . Son elemanlar için seçim sıralaması kullanılır.
jww


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

Bash / linux ruhu içinde, her adım için en iyi komut satırı aracını kullanırdım. sortana işi yapar ancak boşluk yerine satırsonu ile ayrılması gerekir, bu nedenle yukarıdaki basit boru hattı basitçe şunları yapar:

Yankı dizisi içeriği -> alanı satırsonu yerine değiştir -> sırala

$() sonucu yankılamak

($()) "echoed sonucunu" bir diziye koymak

Not : @sorontar bir belirtildiği gibi comment farklı bir soruya:

Sort = ($ (...)) bölümü "split and glob" operatörünü kullanıyor. Glob'u kapatmalısınız: set -f veya set -o noglob veya shopt -op noglob veya * gibi dizinin bir öğesi bir dosya listesine genişletilir.


Bash / linux ruhunda : Sanırım ruhu hiç anlamadınız. Kodunuz tamamen bozuk (yol adı genişletmesi ve kelime bölme). Bu daha iyi olurdu (Bash≥4): mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)aksi takdirde sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf

Kullandığınız antipatterns şunlardır:: echo ${array[@]} | tr " " "\n"dizi alanları beyaz boşluklar ve glob karakterleri içeriyorsa kırılır. Ayrıca, bir alt kabuk ortaya çıkarır ve işe yaramaz bir dış komut kullanır. Ve nedeniyle echodilsiz olmak, bu dizi ile başlıyorsa kıracak -e, -Eya da -n. Bunun yerine kullanın: printf '%s\n' "${array[@]}". Diğer antipattern: ($())"yankılanan sonucu" bir diziye koymaktır . Kesinlikle değil! Bu, yol adı genişlemesi (globbing) ve kelime bölünmesi nedeniyle kırılan korkunç bir antipattern. Asla bu dehşeti kullanmayın.
gniourf_gniourf

Üst cevap "korkunç antipattern" e sahiptir. Ve kendinizin cevapladığınız soruya başka birinin cevabını küçümsemenin yolu.
michael
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.