Для улучшения ответа SLM вы также можете сделать эту работу для файлов, которые начинаются с числа, создав каталоги 0-9:
mkdir -p output/{A..Z}; mkdir -p output/{0..9}; for i in tstdir/*; do export FILE=$(basename "$i"); LTR=$(echo "${FILE:0:1}" | tr [a-z] [A-Z]); mv "$i" "output/$LTR/$FILE" ; done
sed
для сортировки октетов IP-адреса sed
не имеет встроенной функции -в sort
, но если ваши данные достаточно ограничены диапазоном (, например, с IP-адресами ), вы можете сгенерировать сценарий sed, который вручную реализует простой пузырьковая сортировка . Основной механизм заключается в поиске соседних чисел, которые выходят за пределы -порядка -. Если номера не по порядку, поменяйте их местами.
Сам сценарий sed
содержит две команды поиска -и -перестановки для каждой пары исходящих -из -порядковых номеров :одну для первых двух пар октетов (форсирование наличие завершающего разделителя для обозначения конца третьего октета )и второго для третьей пары октетов (с окончанием 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
Следующий вариант генерирующего скрипта использует маркеры границ слов \<
и \>
, чтобы избавиться от необходимости второй замены. Это также сокращает размер сгенерированного скрипта с 1,3 МБ до чуть менее 900 КБ, а также значительно сокращает время выполнения sed
самого (примерно до 50% -75% от исходного, в зависимости от того, 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'
Использование perl
дает очевидную версию; разделить данные, отсортировать их, снова объединить.
Разделитель должен быть указан дважды (один раз в split
и один раз вjoin
)
например, для,
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)'
Сgawk
(GNUawk
)для функцииasort()
:
gawk -v SEP='*' '{ i=0; split($0, arr, SEP); len=asort(arr);
while ( ++i<=len ){ printf("%s%s", i>1?SEP:"", arr[i]) };
print ""
}' infile
замените *
в качестве разделителя полей в SEP='*'
на ваш разделитель .
Вы также можете выполнить следующую команду в случае одной строки (, потому что лучше не использовать циклы оболочки -для целей обработки текста -)
tr '.' '\n' <<<"$aline" | sort -n | paste -sd'.' -
замените точки.
вашим разделителем.
добавьте -u
к команде sort
выше, чтобы удалить дубликаты.
Примечания:
Возможно, вам придется использовать опцию -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
нет необходимости изменять, он по-прежнему будет обрабатывать их.
Разделение ввода на несколько строк
Используя tr
, вы можете разделить ввод с помощью произвольного разделителя на несколько строк.
Затем этот ввод может быть пропущен черезsort
(с использованием -n
, если ввод является числовым ).
Если вы хотите сохранить разделитель в выводе, вы можете снова использовать tr
, чтобы добавить разделитель обратно.
напр. использование пробела в качестве разделителя
cat input.txt | tr " " "\n" | sort -n | tr "\n" " "
вход:1 2 4 1 4 32 18 3
выход:1 1 2 3 4 4 18 32
Использование Python и аналогичная идея, как в ответе Стивена Харриса:
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
К сожалению, необходимость выполнять ввод-вывод вручную делает его гораздо менее элегантным, чем версия Perl.
Загрузка языка более высокого уровня требует времени.
Для нескольких строк сама оболочка может быть решением.
Мы можем использовать внешнюю команду sort
и команду tr
. Один достаточно эффективен для сортировки строк, а другой эффективен для преобразования одного разделителя в символы новой строки :
#!/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
Что занимает всего 0,2 секунды для того же файла из 6000 строк, упомянутого выше.
Поймите, что <"$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"
и добавьте -i
в параметры Perl, чтобы сделать редактирование файла «на месте»:
#!/bin/bash
perlsort(){ perl -lpi -e '$_=join("'"$1"'",sort {$a <=> $b} split(/['"$1"']/))' "$2"; }
perlsort '.' infile; exit
С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 one -лайнеров
Сценарий 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
На основе
Вот какой-то 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"
.
Возвращает ошибку, если один и тот же разделитель не используется последовательно или два или более разделителя следуют друг за другом.
Если используемый разделитель является специальным символом, он экранируется (, иначе sed
возвращает ошибку ).
Нижеследующее является вариацией ответа Джеффа в том смысле, что он генерирует сценарий sed
, который будет выполнять пузырьковую сортировку, но достаточно отличается, чтобы гарантировать собственный ответ.
Разница в том, что вместо O (n^2 )базовых регулярных выражений генерируется O (n )расширенных регулярных выражений. Результирующий скрипт будет иметь размер около 15 КБ. Время выполнения скрипта 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
Идея сгенерированных регулярных выражений заключается в сопоставлении с образцом чисел, которые меньше каждого целого числа; эти два числа будут отсутствовать -порядка -, поэтому они меняются местами. Регулярные выражения сгруппированы в несколько вариантов ИЛИ. Обратите особое внимание на диапазоны, добавленные к каждому элементу, иногда это {0}
, что означает, что непосредственно -предыдущий элемент должен быть исключен из поиска. Параметры регулярного выражения слева от -до -справа соответствуют числам, которые меньше заданного числа на:
Чтобы пояснить пример, возьмите101
(с дополнительными пробелами для удобства чтения):
s/ \<101\> \. \<(10[0-0][0-9]{0} | [0-9]{1,2})\> / \1.101 /g
Здесь первое чередование допускает числа от 100 до 100;второе чередование допускает от 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; второй допускает от 100 до 149, а последний — от 0 до 99.
Проверка четыре раза в цикле:
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
Этот ответ основан на неправильном понимании вопроса, но в некоторых случаях он все равно оказывается правильным. Если ввод полностью состоит из натуральных чисел и имеет только один разделитель на -строку, (, как и в примере данных в Q. ), он работает правильно.. Он также будет обрабатывать файлы со строками, каждая из которых имеет собственный разделитель, что немного больше, чем просили.
Эта функция оболочки read
из стандартного ввода использует подстановку параметров POSIX для поиска определенного разделителя в каждой строке, (сохраненную в $d
), и использует tr
для замены $d
с новой строкой \n
и sort
с данными этой строки, затем восстанавливает исходные разделители каждой строки:
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 ; }
Применительно к данным, приведенным в ОП:
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
Это должно обрабатывать любой не -цифровой разделитель (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
Для произвольных разделителей:
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