Come ordinare un array in Bash


139

Ho un array in Bash, ad esempio:

array=(a c b f 3 5)

Devo ordinare l'array. Non solo visualizzare il contenuto in modo ordinato, ma per ottenere un nuovo array con gli elementi ordinati. Il nuovo array ordinato può essere completamente nuovo o vecchio.

Risposte:


208

Non hai davvero bisogno di tutto quel codice:

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

Supporta spazi bianchi negli elementi (purché non sia una nuova riga) e funziona in Bash 3.x.

per esempio:

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

Nota: @sorontar ha sottolineato che è necessaria attenzione se gli elementi contengono caratteri jolly come *o ?:

La parte ordinata = ($ (...)) utilizza l'operatore "split and glob". È necessario disattivare glob: set -foppure set -o noglobo shopt -op noglobo un elemento dell'array come *verrà espanso in un elenco di file.

Cosa sta succedendo:

Il risultato è il culmine di sei cose che accadono in questo ordine:

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

Prima il IFS=$'\n'

Questa è una parte importante della nostra operazione che influenza il risultato di 2 e 5 nel modo seguente:

Dato:

  • "${array[*]}" si espande ad ogni elemento delimitato dal primo carattere di IFS
  • sorted=() crea elementi suddividendo su ogni personaggio di IFS

IFS=$'\n' imposta le cose in modo tale che gli elementi vengano espansi utilizzando una nuova linea come delimitatore, e successivamente creati in modo tale che ogni linea diventi un elemento. (es. divisione su una nuova linea.)

La delimitazione da una nuova riga è importante perché è così che sortfunziona (ordinamento per riga). Dividere solo una nuova linea non è altrettanto importante, ma è necessario preservare gli elementi che contengono spazi o schede.

Il valore predefinito di IFSè uno spazio , una scheda , seguito da una nuova riga e non sarebbe adatto alla nostra operazione.

Successivamente, la sort <<<"${array[*]}"parte

<<<, chiamato qui stringhe , accetta l'espansione di "${array[*]}", come spiegato sopra, e lo inserisce nell'input standard di sort.

Con il nostro esempio, sortviene fornita la seguente stringa:

a c
b
f
3 5

Da una sort specie , produce:

3 5
a c
b
f

Successivamente, la sorted=($(...))parte

La $(...)parte, chiamata sostituzione dei comandi , fa sì che il suo contenuto ( sort <<<"${array[*]}) venga eseguito come un normale comando, mentre prende l' output standard risultante come letterale che va ovunque $(...).

Nel nostro esempio, questo produce qualcosa di simile alla semplice scrittura:

sorted=(3 5
a c
b
f
)

sorted quindi diventa un array creato dividendo questo valore letterale su ogni nuova riga.

Infine, il unset IFS

Questo reimposta il valore IFSal valore predefinito ed è solo una buona pratica.

È per garantire che non causiamo problemi con tutto ciò che si basa IFSpiù avanti nella nostra sceneggiatura. (Altrimenti dovremmo ricordare che abbiamo cambiato le cose - qualcosa che potrebbe non essere pratico per script complessi.)


2
@xxo senza il IFS, dividerà i tuoi elementi in piccoli pezzi se contengono spazi bianchi. Prova ad es. Con IFS=$'\n' omesso e guarda!
antak,

3
Molto bella. Potresti spiegare per l'utente medio bash come funziona questa soluzione?
u32004,

2
Ora, con il IFS, divide i tuoi elementi in piccoli pezzi se hanno solo un particolare tipo di spazio bianco al suo interno. Buona; non perfetto :-)
Espiazione limitata il

7
È unset IFSnecessario? Ho pensato di anteporre IFS=a un comando solo la modifica a quel comando, tornando automaticamente al suo valore precedente in seguito.
Segna H il

10
@MarkH È necessario perché sorted=()non è un comando ma piuttosto una seconda assegnazione di variabili.
antak,

35

Risposta originale:

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

produzione:

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

Nota che questa versione gestisce valori che contengono caratteri speciali o spazi bianchi ( tranne le nuove righe)

Nota readarray è supportato in bash 4+.


Modifica In base al suggerimento di @Dimitre l'ho aggiornato a:

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

che ha il vantaggio di comprendere anche gli elementi di ordinamento con i caratteri di nuova riga incorporati correttamente. Sfortunatamente, come segnalato correttamente da @ruakh, ciò non significa che il risultato readarraysarebbe corretto , perché readarraynon ha alcuna opzione da usare al NULposto delle normali righe come separatori di linee.


5
Bene, va anche notato che readarray è disponibile dalla versione 4 di bash. Potrebbe essere abbreviato un po ':readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov,

1
@Dimitre: ho preso il tuo suggerimento e ho corretto la gestione degli spazi bianchi in modo che funzionasse con qualsiasi cosa (usando internamente i delimitatori nullchar). Saluti
vedi il

1
Sì, sort -zè un miglioramento utile, suppongo che l' -zopzione sia un'estensione di ordinamento GNU.
Dimitre Radoulov,

2
Se si desidera gestire le nuove righe incorporate, è possibile eseguire il rollup del proprio readarray. Ad esempio: sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). Questo funziona anche se stai usando bash v3 invece di bash v4, perché readarray non è disponibile in bash v3.
Bob Bell,

1
@ user1527227 È il reindirizzamento dell'input ( <) combinato con la sostituzione del processo <(...) . O per dirla in modo intuitivo: perché (printf "bla")non è un file.
Sehe

33

Ecco una pura implementazione di Bash Quicksort:

#!/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[@]}" )
}

Utilizzare come, ad es.

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

Questa implementazione è ricorsiva ... quindi ecco un quicksort iterativo:

#!/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
}

In entrambi i casi, puoi cambiare l'ordine che usi: ho usato confronti di stringhe, ma puoi usare confronti aritmetici, confrontare i tempi di modifica dei file wrt, ecc. Basta usare il test appropriato; puoi persino renderlo più generico e utilizzarlo con un primo argomento che è la funzione di test utilizzata, ad es.

#!/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
}

Quindi puoi avere questa funzione di confronto:

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

e usa:

$ qsort compare_mtime *
$ declare -p qsort_ret

fare in modo che i file nella cartella corrente siano ordinati in base all'ora della modifica (prima il più recente).

NOTA. Queste funzioni sono pure Bash! niente utility esterne e niente subshells! sono sicuri con tutti i simboli divertenti che potresti avere (spazi, caratteri di nuova riga, caratteri glob, ecc.).


1
Complimenti per un impressionante Bashing che offre una grande flessibilità rispetto agli elementi di input e ai criteri di ordinamento. Se l'ordinamento basato su linea con le opzioni di ordinamento sortofferte è sufficiente, una soluzione sort+ read -asarà più veloce a partire da circa, diciamo, 20 articoli e sempre più e significativamente più veloce più elementi hai a che fare. Ad esempio, sul mio iMac di fine 2012 con OSX 10.11.1 con Fusion Drive: array di 100 elementi: ca. 0,03 secondi ( qsort()) vs. ca. 0,005 secondi ( sort+ read -a); Serie di 1000 elementi: ca. 0,375 secondi ( qsort()) vs. ca. 0,014 secondi ( sort+ read -a).
mklement0

Bello. Ricordo l'ordinamento rapido dai tempi del college ma cercherò anche l'ordinamento delle bolle. Per le mie esigenze di ordinamento ho il primo e il secondo elemento che formano la chiave seguito da un elemento di dati (che potrò espandere in seguito). Il codice potrebbe essere migliorato con il numero di elementi chiave (parm1) e il numero di elementi dati (parm2). Per OP i parametri sarebbero 1 e 0. Per me i parametri sarebbero 2 e 1. Sotto ogni aspetto la tua risposta ha più promesse.
WinEunuuchs2Unix

1
Con un set di dati di interi di stringa non registrati che ho trovato if [ "$i" -lt "$pivot" ]; thenera necessario altrimenti il ​​"2" <"10" risolto è tornato vero. Credo che questo sia POSIX vs. lessicografico; o forse Inline Link .
Page2PagePro

27

Se non è necessario gestire caratteri shell speciali negli elementi dell'array:

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

Con bash avrai comunque bisogno di un programma di ordinamento esterno.

Con zsh non sono necessari programmi esterni e speciali caratteri shell sono facilmente gestibili:

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

ksh deve set -sordinare ASCIIbeticamente .


Informazioni sullo sfondo molto belle. Vorrei quasi chiedere una demo su come ksh userebbe il flag set -s ... ma poi di nuovo, la domanda è su bash, quindi sarebbe piuttosto fuori tema
vedi il

Questo dovrebbe funzionare con la maggior parte delle implementazioni di KornShell (ad esempio ksh88 e pdksh ): set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" E, naturalmente, il comando set ripristinerà gli eventuali parametri posizionali correnti.
Dimitre Radoulov,

Sei una vera fonte di conoscenza delle conchiglie. Sono sicuro che devi avere la memoria della fotografia o qualcosa del genere, perché questo tipo di sottili differenze sfuggono alla maggior parte degli altri membri della specie umana :), +1 per il pacchetto completo di informazioni
vedi

10

tl; dr :

Ordina l'array a_ine archivia il risultato a_out(gli elementi non devono avere righe incorporate [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)

Vantaggi rispetto alla soluzione di antak :

  • Non devi preoccuparti del globbing accidentale (interpretazione accidentale degli elementi dell'array come pattern di file), quindi non è necessario alcun comando aggiuntivo per disabilitare il globbing ( set -fe set +fripristinarlo in seguito).

  • Non c'è bisogno di preoccuparsi di reset IFScon unset IFS. [2]


Lettura opzionale: spiegazione e codice di esempio

Quanto sopra combina il codice Bash con un'utilità esterna sortper una soluzione che funziona con elementi a riga singola arbitrari e ordinamento lessicale o numerico (facoltativamente per campo) :

  • Prestazioni : per circa 20 elementi o più , questo sarà più veloce di una soluzione Bash pura, in modo significativo e sempre più una volta superati i circa 100 elementi.
    (Le soglie esatte dipenderanno dall'input, dalla macchina e dalla piattaforma specifici.)

    • Il motivo per cui è veloce è che evita i loop Bash .
  • printf '%s\n' "${a_in[@]}" | sort esegue l'ordinamento (lessicamente, di default - vedi sortle specifiche POSIX ):

    • "${a_in[@]}"si espande in modo sicuro agli elementi dell'array a_income singoli argomenti , qualunque essi contengano (incluso lo spazio).

    • printf '%s\n' quindi stampa ogni argomento, ovvero ogni elemento dell'array, sulla propria riga, così com'è.

  • Si noti l' uso di una sostituzione di processo ( <(...)) per fornire l'output ordinato come input a read/ readarray(tramite reindirizzamento a stdin, <), poiché read/ readarraydeve essere eseguito nella shell corrente (non deve essere eseguito in una subshell ) affinché la variabile di output a_outsia visibile alla shell corrente (affinché la variabile rimanga definita nel resto dello script).

  • Lettura sortdell'output in una variabile array :

    • Bash v4 +: readarray -t a_outlegge le singole righe emesse dagli sortelementi della variabile array a_out, senza includere il trailing \nin ciascun elemento ( -t).

    • Bash v3: readarraynon esiste, quindi readdeve essere usato:
      IFS=$'\n' read -d '' -r -a a_outdice readdi leggere nella -avariabile array ( ) a_out, leggendo l'intero input, attraverso le righe ( -d ''), ma suddividendolo in elementi dell'array per newline ( IFS=$'\n'. $'\n', Che produce una newline letterale (LF ), è una cosiddetta stringa quotata ANSI C ).
      ( -r, un'opzione che dovrebbe essere praticamente sempre utilizzata con read, disabilita la gestione imprevista dei \caratteri.)

Codice di esempio annotato:

#!/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[@]}"

A causa dell'uso sortsenza opzioni, questo produce un ordinamento lessicale (le cifre si ordinano prima delle lettere e le sequenze di cifre vengono trattate in modo lessicale, non come numeri):

*
10
5
a c
b
f

Se desideri l' ordinamento numerico per il 1 ° campo, utilizzeresti sort -k1,1ninvece di solo sort, il che produce (l'ordinamento non numerico prima dei numeri e l'ordinamento numerico corretto):

*
a c
b
f
5
10

[1] elementi con manico per capo ', utilizzare la seguente variante (Bash v4 +, con GNU sort ):
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
La risposta utile di Michał Górny ha una soluzione Bash v3.

[2] Mentre IFS è impostato nella variante di Bash v3, la modifica è nell'ambito del comando .
Al contrario, ciò che segue IFS=$'\n' nella risposta di Antak è un compito piuttosto che un comando, nel qual caso il IFScambiamento è globale .


8

Nel viaggio di 3 ore in treno da Monaco a Francoforte (che avevo difficoltà a raggiungere perché l'Oktoberfest inizia domani), stavo pensando al mio primo post. L'impiego di un array globale è un'idea molto migliore per una funzione di ordinamento generale. La seguente funzione gestisce le stringhe arbitrarie (newline, spazi vuoti ecc.):

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[@]}

Questo stampa:

3 5 a b c z y

Lo stesso output viene creato da

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

Si noti che probabilmente Bash utilizza internamente i puntatori intelligenti, quindi l'operazione di scambio potrebbe essere economica (anche se ne dubito). Tuttavia, bubble_sortdimostra che funzioni più avanzate come merge_sortsono anche alla portata del linguaggio shell.


5
Ordinamento delle bolle? Wow .. Obama dice che "il bubble sort sarebbe la strada sbagliata" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino,

1
Bene, sembra che mentre l'O-guy voleva essere intelligente non avesse percepito che questa non era una domanda del 50/50. Un predecessore nella posizione di O-guy, diciamo che il B-guy, una volta ha fatto molto meglio (Reynoldsburg, Ohio, ottobre 2000): "Penso che se sai cosa credi, rende molto più facile rispondere alle domande Non posso rispondere alla tua domanda. " Quindi questo B-guy sa davvero qualcosa sulla logica booleana. L'O-guy no.
Andreas Spindler,

La funzione potrebbe essere resa più facilmente trasportabile rendendo BSORT un array locale con un nameref a qualunque array debba essere ordinato. cioè local -n BSORT="$1"all'inizio della funzione. Quindi è possibile eseguire bubble_sort myarrayper ordinare myarray .
johnraff,

7

Un'altra soluzione che utilizza esterni sortpiviali e con eventuali caratteri speciali (eccetto NULs :)). Dovrebbe funzionare con bash-3.2 e GNU o BSD sort(purtroppo POSIX non include -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)

Primo sguardo al reindirizzamento di input alla fine. Stiamo usando il printfbuilt-in per scrivere gli elementi dell'array, a terminazione zero. La quotazione assicura che gli elementi dell'array vengano passati così come sono e i dettagli specifici della shell printfinducono a riutilizzare l'ultima parte della stringa di formato per ciascun parametro rimanente. Cioè, è equivalente a qualcosa del tipo:

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

L'elenco di elementi con terminazione null viene quindi passato a sort. L' -zopzione provoca la lettura di elementi con terminazione null, l'ordinamento e l'output con terminazione null. Se hai bisogno di ottenere solo gli elementi unici, puoi passare -upoiché è più portatile di uniq -z. Il LC_ALL=Cassicura ordinamento stabile indipendentemente locale - talvolta utile per gli script. Se si desidera sortrispettare le impostazioni internazionali, rimuoverlo.

Il <()costrutto ottiene il descrittore da leggere dalla pipeline generata e <reindirizza l'input standard del whileloop su di esso. Se è necessario accedere allo standard input all'interno della pipe, è possibile utilizzare un altro descrittore - esercizio per il lettore :).

Ora, torniamo all'inizio. Il readbuilt-in legge l'output dallo stdin reindirizzato. L'impostazione di vuoto IFSdisabilita la suddivisione delle parole che non è necessaria qui - di conseguenza, readlegge l'intera 'linea' di input nella singola variabile fornita. -rL'opzione disabilita l'elaborazione di escape indesiderata anche qui. Infine, -d ''imposta il delimitatore di riga su NUL, ovvero indica readdi leggere le stringhe con terminazione zero.

Di conseguenza, il ciclo viene eseguito una volta per ogni successivo elemento dell'array con terminazione zero, con il valore memorizzato in e. L'esempio inserisce semplicemente gli elementi in un altro array ma potresti preferire elaborarli direttamente :).

Certo, questo è solo uno dei tanti modi per raggiungere lo stesso obiettivo. A mio avviso, è più semplice che implementare l'algoritmo di ordinamento completo in bash e in alcuni casi sarà più veloce. Gestisce tutti i caratteri speciali, compresi i newline e dovrebbe funzionare sulla maggior parte dei sistemi comuni. Ancora più importante, potrebbe insegnarti qualcosa di nuovo e di straordinario su bash :).


Ottima soluzione e spiegazione molto utile, grazie. Un'estensione: senza impostare IFS su vuoto, anche gli spazi bianchi iniziali verranno eliminati, anche se in caso contrario non è stata eseguita la divisione delle parole.
Dirk Herrmann,

Invece di introdurre la variabile locale ee impostare IFS vuoti, utilizzare la variabile REPLY.
Robin A. Meade,

2

prova questo:

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

L'output sarà:

3
5
un'
B
c
f

Problema risolto.


3
Dovrebbe modificare questo per mettere l'output in un nuovo array per rispondere pienamente alla sua domanda.
Peter Oram,

2

Se è possibile calcolare un numero intero univoco per ciascun elemento dell'array, in questo modo:

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 -> $?"

quindi, è possibile utilizzare questi numeri interi come indici di array, poiché Bash utilizza sempre array di tipo sparse, quindi non è necessario preoccuparsi degli indici non utilizzati:

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

echo "${sorted[@]}"
  • Professionisti. Veloce.
  • Cons. Gli elementi duplicati vengono uniti e può essere impossibile mappare i contenuti a numeri interi univoci a 32 bit.

Tecnica interessante, ho usato una variante per trovare valori max / min senza confronto / ordinamento espliciti. Ma l' aggiunta non ponderata senza riguardo alla lunghezza non funzionerà: "z" ordina prima di "aaaa", quindi non puoi usarlo per le parole come mostrato sopra.
mr.spuratic

2

ordinamento minimo:

#!/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[@]}

i contenuti dell'eco di new_array saranno:

3 5 a b c f

1

C'è una soluzione alternativa per il solito problema di spazi e newline:

Utilizzare un carattere che non è nell'array originale (come $'\1'o $'\4'o simile).

Questa funzione esegue il lavoro:

# 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
       }

Questo ordinerà l'array:

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

Ciò lamenterà che l'array di origine contiene il carattere di soluzione alternativa:

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

descrizione

  • Abbiamo impostato due variabili locali wa(soluzione alternativa) e un IFS nullo
  • Quindi (con ifs null) testiamo l'intero array $*.
  • Non contiene alcun carattere preoccupato [[ $* =~ [$wa] ]].
  • In tal caso, genera un messaggio e segnala un errore: exit 1
  • Evita le espansioni del nome file: set -f
  • Imposta un nuovo valore di IFS ( IFS=$'\n') una variabile di ciclo xe una nuova riga var ( nl=$'\n').
  • Stampiamo tutti i valori degli argomenti ricevuti (l'array di input $@).
  • ma sostituiamo qualsiasi nuova riga con il carattere alternativo "${@//$nl/$wa}".
  • invia quei valori per essere ordinati sort -n.
  • e riposizionare tutti i valori ordinati negli argomenti posizionali set --.
  • Quindi assegniamo ogni argomento uno per uno (per preservare le nuove righe).
  • in un ciclo for x
  • a un nuovo array: sorted+=(…)
  • all'interno delle virgolette per preservare qualsiasi newline esistente.
  • ripristinare la soluzione alternativa a una nuova riga "${x//$wa/$nl}".
  • fatto

1

Questa domanda sembra strettamente correlata. E a proposito, ecco una fusione in Bash (senza processi esterni):

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

Non sono convinto che avrai bisogno di un programma di smistamento esterno in Bash.

Ecco la mia implementazione per il semplice algoritmo bubble-sort.

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[@]})"

Questo stampa:

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

L'ordinamento delle bolle è O(n^2). Mi sembra di ricordare che la maggior parte degli algoritmi di ordinamento usano un O(n lg(n))fino alla dozzina finale di elementi o giù di lì. Per gli elementi finali, viene utilizzato l'ordinamento per selezione.
jww


-1

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

Nello spirito di bash / linux, instraderei il miglior strumento da riga di comando per ogni passaggio. sortsvolge il lavoro principale ma necessita di input separati da newline anziché da spazio, quindi la semplice pipeline sopra semplicemente fa:

Eco array content -> sostituisci spazio con newline -> ordina

$() è fare eco al risultato

($()) consiste nel mettere il "risultato eco" in un array

Nota : come menzionato @sorontar in un commento a una domanda diversa:

La parte ordinata = ($ (...)) utilizza l'operatore "split and glob". Dovresti disattivare glob: set -f o set -o noglob o shopt -op noglob o un elemento dell'array come * verrà espanso in un elenco di file.


Nello spirito di bash / linux : immagino tu non abbia capito affatto lo spirito. Il tuo codice è completamente rotto (espansione del percorso e suddivisione in parole). Questo sarebbe meglio (Bash≥4):, mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)altrimenti sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf

Gli antipattern che stai usando sono:: echo ${array[@]} | tr " " "\n"questo si interromperà se i campi dell'array contengono spazi bianchi e caratteri glob. Inoltre, genera una subshell e utilizza un comando esterno inutile. E a causa di echoessere stupido, si interromperà se l'array inizia con -e, -Eo -n. Invece utilizzare: printf '%s\n' "${array[@]}". L'altro antipattern è: ($())è quello di mettere il "risultato eco" in un array . Certamente no! questo è un orribile antipasto che si rompe a causa dell'espansione del nome del percorso (globbing) e della divisione delle parole. Non usare mai questo orrore.
gniourf_gniourf

La risposta migliore ha l '"orribile antipasto". E un modo per andare a votare la risposta di qualcun altro alla domanda a cui hai risposto tu stesso.
michael
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.