任意の文字で区切られた1行(または複数行)の数字があります。区切り文字を保持しながら、各行の項目を数値でソートするためにどのUNIXツールを使用できますか?
例は次のとおりです。
10 50 23 42
;並べ替え:10 23 42 50
10.1.200.42
;並べ替え:1.10.42.200
1,100,330,42
;並べ替え:1,42,100,330
400|500|404
;並べ替え:400|404|500
区切り文字は任意なので、任意の1文字の区切り文字を使用して回答を提供(または拡張)してください。
あなたはこれを達成することができます:
tr '.' '\n' <<<"$aline" | sort -n | paste -sd'.' -
dots.
を区切り文字に置き換えます。
上記のsort
コマンドに -u
を追加して、重複を削除します。
またはgawk
([〜#〜] gnu [〜#〜]awk
)を使用すると、多くの行を処理できますが、上記も拡張できます。
gawk -v SEP='*' '{ i=0; split($0, arr, SEP);
while ( ++i<=asort(arr) ){ printf("%s%s", i>1?SEP:"", arr[i]) };
print ""
}' infile
*
のフィールド区切り文字としてのSEP='*'
を区切り文字に置き換えます。
メモ:
数値のクラス(整数、浮動小数点、科学、16進数など)を処理するには、-g, --general-numeric-sort
ではなくsort
の-n, --numeric-sort
オプションを使用する必要がある場合があります。
$ aline='2e-18,6.01e-17,1.4,-4,0xB000,0xB001,23,-3.e+11'
$ tr ',' '\n' <<<"$aline" |sort -g | paste -sd',' -
-3.e+11,-4,2e-18,6.01e-17,1.4,23,0xB000,0xB001
awk
では変更する必要はありませんが、それらは引き続き処理されます。
Perl
を使用すると、明らかなバージョンがあります。データを分割し、並べ替え、再び結合します。
区切り文字は2回リストする必要があります(split
に1回、join
に1回)。
たとえば,
Perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
そう
echo 1,100,330,42 | Perl -lpi -e '$_=join(",",sort {$a <=> $b} split(/,/))'
1,42,100,330
split
は正規表現であるため、文字を引用する必要がある場合があります。
echo 10.1.200.42 | Perl -lpi -e '$_=join(".",sort {$a <=> $b} split(/\./))'
1.10.42.200
-a
および-F
オプションを使用することで、分割を削除できます。前と同じように-p
ループを使用して、結果を$_
に設定すると、自動的に印刷されます。
Perl -F'/\./' -aple '$_=join(".", sort {$a <=> $b} @F)'
Pythonおよび Stephen Harrisの回答)と同様のアイデアを使用してください :
python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' <delmiter>
だから次のようなもの:
$ cat foo
10.129.3.4
1.1.1.1
4.3.2.1
$ python3 -c 'import sys; c = sys.argv[1]; sys.stdout.writelines(map(lambda x: c.join(sorted(x.strip().split(c), key=int)) + "\n", sys.stdin))' . < foo
3.4.10.129
1.1.1.1
1.2.3.4
悲しいことに、手動でI/Oを行う必要があるため、これはPerlバージョンよりもはるかに洗練されていません。
より高いレベルの言語のロードには時間がかかります。
数行については、シェル自体が解決策になる場合があります。
外部コマンドsort
およびコマンドtr
を使用できます。 1つは行のソートに非常に効率的で、もう1つは1つの区切り文字を改行に変換するのに効果的です。
#!/bin/bash
shsort(){
while IFS='' read -r line; do
echo "$line" | tr "$1" '\n' |
sort -n | paste -sd "$1" -
done <<<"$2"
}
shsort ' ' '10 50 23 42'
shsort '.' '10.1.200.42'
shsort ',' '1,100,330,42'
shsort '|' '400|500|404'
shsort ',' '3 b,2 x,45 f,*,8jk'
shsort '.' '10.128.33.6
128.17.71.3
44.32.63.1'
<<<
のみを使用するため、これにはbashが必要です。それがヒアドキュメントに置き換えられた場合、解決策はposixに対して有効です。
これは、タブ、スペース、またはシェルグロブ文字(*
、?
、[
)でフィールドを並べ替えることができます。各行がソートされているため、改行ではありません。
ファイル名を処理して次のように呼び出すには、<<<"$2"
を<"$2"
に変更します。
shsort '.' infile
デリミタはファイル全体で同じです。それが制限である場合は、改善することができます。
ただし、6000行しかないファイルの処理には15秒かかります。確かに、シェルはファイルを処理するための最良のツールではありません。
数行を超える(数十行を超える)場合は、実際のプログラミング言語を使用することをお勧めします。 awkソリューションは次のとおりです。
#!/bin/bash
awksort(){
gawk -v del="$1" '{
split($0, fields, del)
l=asort(fields)
for(i=1;i<=l;i++){
printf( "%s%s" , (i==0)?"":del , fields[i] )
}
printf "\n"
}' <"$2"
}
awksort '.' infile
上記と同じ6000行のファイルで0.2秒しかかかりません。
ファイルの<"$2"
を、シェル変数内の行の<<<"$2"
に戻すことができることを理解してください。
最速の解決策はPerlです。
#!/bin/bash
perlsort(){ Perl -lp -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' <<<"$2"; }
perlsort ' ' '10 50 23 42'
perlsort '.' '10.1.200.42'
perlsort ',' '1,100,330,42'
perlsort '|' '400|500|404'
perlsort ',' '3 b,2 x,45 f,*,8jk'
perlsort '.' '10.128.33.6
128.17.71.3
44.32.63.1'
ファイルを並べ替える場合は、<<<"$a"
を単純に"$a"
に変更し、Perlオプションに-i
を追加して、ファイルエディションを「適切な場所」にします。
#!/bin/bash
perlsort(){ Perl -lpi -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' "$2"; }
perlsort '.' infile; exit
Bashスクリプト:
#!/usr/bin/env bash
join_by(){ local IFS="$1"; shift; echo "$*"; }
IFS="$1" read -r -a tokens_array <<< "$2"
IFS=$'\n' sorted=($(sort -n <<<"${tokens_array[*]}"))
join_by "$1" "${sorted[@]}"
例:
$ ./sort_delimited_string.sh "." "192.168.0.1"
0.1.168.192
に基づく
sed
を使用してIPアドレスのオクテットをソートするsed
にはsort
関数が組み込まれていませんが、データが範囲内で(IPアドレスなどで)十分に制約されている場合は、手動で簡単に実装するsedスクリプトを生成できます- バブルソート 。基本的なメカニズムは、順序が乱れている隣接する数値を探すことです。番号が順番になっていない場合は、入れ替えます。
sed
スクリプト自体には、順不同番号のペアごとに2つの検索およびスワップコマンドが含まれています。1つはオクテットの最初の2つのペア用です(末尾の区切り文字を強制して、 3番目のオクテット)、3番目のオクテットのペアの2番目(最後はEOL)。スワップが発生すると、プログラムはスクリプトの先頭に分岐し、順序が狂っている数値を探します。それ以外の場合は終了します。
生成されたスクリプトは、部分的には次のとおりです。
$ head -n 3 generated.sed
:top
s/255\.254\./254.255./g; s/255\.254$/254.255/
s/255\.253\./253.255./g; s/255\.253$/253.255/
# ... middle of the script omitted ...
$ tail -n 4 generated.sed
s/2\.1\./1.2./g; s/2\.1$/1.2/
s/2\.0\./0.2./g; s/2\.0$/0.2/
s/1\.0\./0.1./g; s/1\.0$/0.1/
ttop
このアプローチでは、ピリオドをエスケープする必要のある区切り文字としてハードコードします。そうしないと、正規表現構文に「特別」になり、任意の文字を使用できます。
このようなsedスクリプトを生成するために、このループは次のことを行います。
#!/bin/bash
echo ':top'
for (( n = 255; n >= 0; n-- )); do
for (( m = n - 1; m >= 0; m-- )); do
printf '%s; %s\n' "s/$n\\.$m\\./$m.$n./g" "s/$n\\.$m\$/$m.$n/"
done
done
echo 'ttop'
そのスクリプトの出力を別のファイル、たとえばsort-ips.sed
にリダイレクトします。
サンプルの実行は次のようになります。
ip=$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256)).$((RANDOM % 256))
printf '%s\n' "$ip" | sed -f sort-ips.sed
次の生成スクリプトのバリエーションでは、Word境界マーカー\<
および\>
を使用して、2番目の置換の必要性を取り除きます。これにより、生成されたスクリプトのサイズも1.3 MBから900 KB未満に削減され、sed
自体の実行時間が大幅に短縮されます(元のsed
実装が使用されています):
#!/bin/bash
echo ':top'
for (( n = 255; n >= 0; --n )); do
for (( m = n - 1; m >= 0; --m )); do
printf '%s\n' "s/\\<$n\\>\\.\\<$m\\>/$m.$n/g"
done
done
echo 'ttop'
ここでは、区切り文字をそれ自体で推測するbashをいくつか示します。
#!/bin/bash
delimiter="${1//[[:digit:]]/}"
if echo $delimiter | grep -q "^\(.\)\1\+$"
then
delimiter="${delimiter:0:1}"
if [[ -z $(echo $1 | grep "^\([0-9]\+"$delimiter"\([0-9]\+\)*\)\+$") ]]
then
echo "You seem to have empty fields between the delimiters."
exit 1
fi
if [[ './\' == *$delimiter* ]]
then
n=$( echo $1 | sed "s/\\"$delimiter"/\\n/g" | sort -n | tr '\n' ' ' | sed -e "s/\\s/\\"$delimiter"/g")
else
n=$( echo $1 | sed "s/"$delimiter"/\\n/g" | sort -n | tr '\n' ' ' | sed -e "s/\\s/"$delimiter"/g")
fi
echo ${n%$delimiter}
exit 0
else
echo "The string does not consist of digits separated by one unique delimiter."
exit 1
fi
効率的でもクリーンでもないかもしれませんが、機能します。
bash my_script.sh "00/00/18/29838/2"
のように使用します。
同じ区切り文字が一貫して使用されていない場合、または2つ以上の区切り文字が連続している場合は、エラーを返します。
使用されている区切り文字が特殊文字の場合、エスケープされます(それ以外の場合、sed
はエラーを返します)。
この回答はQ.の誤解に基づいていますが、いずれにせよそれが正しい場合もあります。入力が完全に 自然数 であり、1行のみの区切り文字がある場合(( Q.)、それは正しく動作します。また、それぞれが独自の区切り文字を持つ行を含むファイルも処理します。これは、要求されたものよりも少し多くなります。
このシェル関数read
sは標準入力から、POSIXパラメーター置換を使用して、各行の特定の区切り文字を検索します($d
)を使用し、tr
を使用して$d
を改行\n
およびsort
sでその行のデータに置き換え、各行の元の区切り文字を復元します。
sdn() { while read x; do
d="${x#${x%%[^0-9]*}}" d="${d%%[0-9]*}"
x=$(echo -n "$x" | tr "$d" '\n' | sort -g | tr '\n' "$d")
echo ${x%?}
done ; }
[〜#〜] op [〜#〜]で指定されたデータに適用されます:
printf "%s\n" "10 50 23 42" "10.1.200.42" "1,100,330,42" "400|500|404" | sdn
出力:
10 23 42 50
1.10.42.200
1,42,100,330
400|404|500
任意の区切り文字の場合:
Perl -lne '
@list = /\D+|\d+/g;
@sorted = sort {$a <=> $b} grep /\d/, @list;
for (@list) {$_ = shift@sorted if /\d/};
print @list'
次のような入力で:
5,4,2,3
6|5,2|4
There are 10 numbers in those 3 lines
それは与えます:
2,3,4,5
2|4,5|6
There are 3 numbers in those 10 lines
以下は、バブルソートを実行するsed
スクリプトを生成するという意味での Jeffの回答 のバリエーションですが、独自の回答を保証するのに十分な違いがあります。
違いは、O(n ^ 2)基本正規表現を生成する代わりに、O(n)拡張正規表現を生成することです。結果のスクリプトは約15 KBの大きさになります。実行時間はsed
スクリプトは、ほんの一瞬です(スクリプトの生成には少し時間がかかります)。
ドットで区切られた正の整数の並べ替えに制限されていますが、整数のサイズに制限されていません(255
(メインループ)、または整数の数。区切り文字は、delim='.'
コード内。
正規表現を正しくするために頭を使ったので、別の日の詳細を説明します。
#!/bin/bash
# This function creates a extended regular expression
# that matches a positive number less than the given parameter.
lt_pattern() {
local n="$1" # Our number.
local -a res # Our result, an array of regular expressions that we
# later join into a string.
for (( i = 1; i < ${#n}; ++i )); do
d=$(( ${n: -i:1} - 1 )) # The i:th digit of the number, from right to left, minus one.
if (( d >= 0 )); then
res+=( "$( printf '%d[0-%d][0-9]{%d}' "${n:0:-i}" "$d" "$(( i - 1 ))" )" )
fi
done
d=${n:0:1} # The first digit of the number.
if (( d > 1 )); then
res+=( "$( printf '[1-%d][0-9]{%d}' "$(( d - 1 ))" "$(( ${#n} - 1 ))" )" )
fi
if (( n > 9 )); then
# The number is 10 or larger.
res+=( "$( printf '[0-9]{1,%d}' "$(( ${#n} - 1 ))" )" )
fi
if (( n == 1 )); then
# The number is 1. The only thing smaller is zero.
res+=( 0 )
fi
# Join our res array of expressions into a '|'-delimited string.
( IFS='|'; printf '%s\n' "${res[*]}" )
}
echo ':top'
delim='.'
for (( n = 255; n > 0; --n )); do
printf 's/\\<%d\\>\\%s\\<(%s)\\>/\\1%s%d/g\n' \
"$n" "$delim" "$( lt_pattern "$n" )" "$delim" "$n"
done
echo 'ttop'
スクリプトは次のようになります。
$ bash generator.sh >script.sed
$ head -n 5 script.sed
:top
s/\<255\>\.\<(25[0-4][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.255/g
s/\<254\>\.\<(25[0-3][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.254/g
s/\<253\>\.\<(25[0-2][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.253/g
s/\<252\>\.\<(25[0-1][0-9]{0}|2[0-4][0-9]{1}|[1-1][0-9]{2}|[0-9]{1,2})\>/\1.252/g
$ tail -n 5 script.sed
s/\<4\>\.\<([1-3][0-9]{0})\>/\1.4/g
s/\<3\>\.\<([1-2][0-9]{0})\>/\1.3/g
s/\<2\>\.\<([1-1][0-9]{0})\>/\1.2/g
s/\<1\>\.\<(0)\>/\1.1/g
ttop
生成された正規表現の背後にある考え方は、各整数より小さい数値のパターンマッチングです。これらの2つの数値は順不同であるため、入れ替えられます。正規表現はいくつかのORオプションにグループ化されています。各項目に追加された範囲に細心の注意を払ってください。時には{0}
は、直前のアイテムが検索から除外されることを意味します。正規表現オプションは、左から右に、指定された数値よりも小さい数値を次のように照合します。
例を詳しく説明するには、101
(読みやすくするためにスペースを追加):
s/ \<101\> \. \<(10[0-0][0-9]{0} | [0-9]{1,2})\> / \1.101 /g
ここでは、最初の代替では100から100までの数値を許可しています。 2番目の代替では、0〜99を使用できます。
別の例は154
:
s/ \<154\> \. \<(15[0-3][0-9]{0} | 1[0-4][0-9]{1} | [0-9]{1,2})\> / \1.154 /g
ここで、最初のオプションは150から153を許可します。 2番目は100から149を許可し、最後は0から99を許可します。
ループで4回テストする:
for test_run in {1..4}; do
nums=$(( RANDOM%256 )).$(( RANDOM%256 )).$(( RANDOM%256 )).$(( RANDOM%256 ))
printf 'nums=%s\n' "$nums"
sed -E -f script.sed <<<"$nums"
done
出力:
nums=90.19.146.232
19.90.146.232
nums=8.226.70.154
8.70.154.226
nums=1.64.96.143
1.64.96.143
nums=67.6.203.56
6.56.67.203
Perl
の場合:
$ # -a to auto-split on whitespace, results in @F array
$ echo 'foo baz v22 aimed' | Perl -lane 'print join " ", sort @F'
aimed baz foo v22
$ # {$a <=> $b} for numeric comparison, {$b <=> $a} will give descending order
$ echo '1,100,330,42' | Perl -F, -lane 'print join ",", sort {$a <=> $b} @F'
1,42,100,330
Ruby
を使用すると、Perl
と多少似ています。
$ # -a to auto-split on whitespace, results in $F array
$ # $F is sorted and then joined using the given string
$ echo 'foo baz v22 aimed' | Ruby -lane 'print $F.sort * " "'
aimed baz foo v22
$ # (&:to_i) to convert string to integer
$ echo '1,100,330,42' | Ruby -F, -lane 'print $F.sort_by(&:to_i) * ","'
1,42,100,330
$ echo '10.1.200.42' | Ruby -F'\.' -lane 'print $F.sort_by(&:to_i) * "."'
1.10.42.200
カスタムコマンドと区切り文字列のみを渡します(正規表現ではありません)。入力にも浮動データがある場合に機能します
$ # by default join uses value of $,
$ sort_line(){ Ruby -lne '$,=ENV["d"]; print $_.split($,).sort_by(&:to_f).join' ; }
$ s='103,14.5,30,24'
$ echo "$s" | d=',' sort_line
14.5,24,30,103
$ s='10.1.200.42'
$ echo "$s" | d='.' sort_line
1.10.42.200
$ # for file input
$ echo '123--87--23' > ip.txt
$ echo '3--12--435--8' >> ip.txt
$ d='--' sort_line <ip.txt
23--87--123
3--8--12--435
Perl
のカスタムコマンド
$ sort_line(){ Perl -lne '$d=$ENV{d}; print join $d, sort {$a <=> $b} split /\Q$d/' ; }
$ s='123^[]$87^[]$23'
$ echo "$s" | d='^[]$' sort_line
23^[]$87^[]$123
さらに読む-私はすでにPerl/Rubyのワンライナーのこの便利なリストを持っていました
これは、数字以外の(0-9)区切り文字を処理する必要があります。例:
x='1!4!3!5!2'; delim=$(echo "$x" | tr -d 0-9 | cut -b1); echo "$x" | tr "$delim" '\n' | sort -g | tr '\n' "$delim" | sed "s/$delim$/\n/"
出力:
1!2!3!4!5