Risposte:
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 -f
oppureset -o noglob
oshopt -op noglob
o un elemento dell'array come*
verrà espanso in un elenco di file.
Il risultato è il culmine di sei cose che accadono in questo ordine:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
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 sort
funziona (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.
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, sort
viene fornita la seguente stringa:
a c
b
f
3 5
Da una sort
specie , produce:
3 5
a c
b
f
sorted=($(...))
parteLa $(...)
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.
unset IFS
Questo reimposta il valore IFS
al valore predefinito ed è solo una buona pratica.
È per garantire che non causiamo problemi con tutto ciò che si basa IFS
più avanti nella nostra sceneggiatura. (Altrimenti dovremmo ricordare che abbiamo cambiato le cose - qualcosa che potrebbe non essere pratico per script complessi.)
IFS
, divide i tuoi elementi in piccoli pezzi se hanno solo un particolare tipo di spazio bianco al suo interno. Buona; non perfetto :-)
unset IFS
necessario? Ho pensato di anteporre IFS=
a un comando solo la modifica a quel comando, tornando automaticamente al suo valore precedente in seguito.
sorted=()
non è un comando ma piuttosto una seconda assegnazione di variabili.
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 readarray
sarebbe corretto , perché readarray
non ha alcuna opzione da usare al NUL
posto delle normali righe come separatori di linee.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
è un miglioramento utile, suppongo che l' -z
opzione sia un'estensione di ordinamento GNU.
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.
<
) combinato con la sostituzione del processo <(...)
. O per dirla in modo intuitivo: perché (printf "bla")
non è un file.
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.).
sort
offerte è sufficiente, una soluzione sort
+ read -a
sarà 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
).
if [ "$i" -lt "$pivot" ]; then
era necessario altrimenti il "2" <"10" risolto è tornato vero. Credo che questo sia POSIX vs. lessicografico; o forse Inline Link .
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 -s
ordinare ASCIIbeticamente .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
E, naturalmente, il comando set ripristinerà gli eventuali parametri posizionali correnti.
tl; dr :
Ordina l'array a_in
e 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 -f
e set +f
ripristinarlo in seguito).
Non c'è bisogno di preoccuparsi di reset IFS
con unset IFS
. [2]
Quanto sopra combina il codice Bash con un'utilità esterna sort
per 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.)
printf '%s\n' "${a_in[@]}" | sort
esegue l'ordinamento (lessicamente, di default - vedi sort
le specifiche POSIX ):
"${a_in[@]}"
si espande in modo sicuro agli elementi dell'array a_in
come 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
/ readarray
deve essere eseguito nella shell corrente (non deve essere eseguito in una subshell ) affinché la variabile di output a_out
sia visibile alla shell corrente (affinché la variabile rimanga definita nel resto dello script).
Lettura sort
dell'output in una variabile array :
Bash v4 +: readarray -t a_out
legge le singole righe emesse dagli sort
elementi della variabile array a_out
, senza includere il trailing \n
in ciascun elemento ( -t
).
Bash v3: readarray
non esiste, quindi read
deve essere usato:
IFS=$'\n' read -d '' -r -a a_out
dice read
di leggere nella -a
variabile 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 sort
senza 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,1n
invece 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 IFS
cambiamento è globale .
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_sort
dimostra che funzioni più avanzate come merge_sort
sono anche alla portata del linguaggio shell.
local -n BSORT="$1"
all'inizio della funzione. Quindi è possibile eseguire bubble_sort myarray
per ordinare myarray .
Un'altra soluzione che utilizza esterni sort
piviali 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 printf
built-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 printf
inducono 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' -z
opzione 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 -u
poiché è più portatile di uniq -z
. Il LC_ALL=C
assicura ordinamento stabile indipendentemente locale - talvolta utile per gli script. Se si desidera sort
rispettare le impostazioni internazionali, rimuoverlo.
Il <()
costrutto ottiene il descrittore da leggere dalla pipeline generata e <
reindirizza l'input standard del while
loop 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 read
built-in legge l'output dallo stdin reindirizzato. L'impostazione di vuoto IFS
disabilita la suddivisione delle parole che non è necessaria qui - di conseguenza, read
legge l'intera 'linea' di input nella singola variabile fornita. -r
L'opzione disabilita l'elaborazione di escape indesiderata anche qui. Infine, -d ''
imposta il delimitatore di riga su NUL, ovvero indica read
di 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 :).
e
e impostare IFS vuoti, utilizzare la variabile REPLY.
prova questo:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
L'output sarà:
3 5 un' B c f
Problema risolto.
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[@]}"
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
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
wa
(soluzione alternativa) e un IFS nullo$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
) una variabile di ciclo x
e una nuova riga var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.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[@]}"
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
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.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
Nello spirito di bash / linux, instraderei il miglior strumento da riga di comando per ogni passaggio. sort
svolge 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.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
altrimenti sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
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 echo
essere stupido, si interromperà se l'array inizia con -e
, -E
o -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.
IFS
, dividerà i tuoi elementi in piccoli pezzi se contengono spazi bianchi. Prova ad es. ConIFS=$'\n'
omesso e guarda!