Как обнулить последовательность целых чисел в bash, чтобы все имели одинаковую ширину?
Мне нужно зациклить некоторые значения,
for i in $(seq $first $last)
do
does something here
done
За $first
а также $last
Мне нужно, чтобы он был фиксированной длины 5. Так что, если вход 1
, мне нужно добавить нули впереди так, чтобы это стало 00001
, Это петли до 99999
например, но длина должна быть 5.
Например: 00002
, 00042
, 00212
, 012312
и так далее.
Любая идея о том, как я могу это сделать?
16 ответов
В вашем конкретном случае, вероятно, проще всего использовать -f
флаг для seq
чтобы получить его для форматирования чисел при выводе списка. Например:
for i in $(seq -f "%05g" 10 15)
do
echo $i
done
выдаст следующий вывод:
00010
00011
00012
00013
00014
00015
В более общем смысле, bash
имеет printf
как встроенный, так что вы можете дополнить вывод нулями следующим образом:
$ i=99
$ printf "%05d\n" $i
00099
Вы можете использовать -v
флаг для сохранения вывода в другой переменной:
$ i=99
$ printf -v j "%05d" $i
$ echo $j
00099
Заметить, что printf
поддерживает немного другой формат seq
так что вам нужно использовать %05d
вместо %05g
,
Проще еще можно просто сделать
for i in {00001..99999}; do
echo $i
done
Если конец последовательности имеет максимальную длину заполнения (например, если вы хотите 5 цифр и команда имеет значение "seq 1 10000"), то вы можете использовать флаг "-w" для seq - он добавляет сам заполнитель.
seq -w 1 10
производить
01
02
03
04
05
06
07
08
09
10
Очень простое использование printf
[jaypal:~/Temp] printf "%05d\n" 1
00001
[jaypal:~/Temp] printf "%05d\n" 2
00002
Используйте awk следующим образом:
awk -v start=1 -v end=10 'BEGIN{for (i=start; i<=end; i++) printf("%05d\n", i)}'
ВЫХОД:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
Обновить:
В качестве чистой альтернативы Bash вы можете сделать это, чтобы получить тот же результат:
for i in {1..10}
do
printf "%05d\n" $i
done
Таким образом, вы можете избежать использования внешней программы seq
который НЕ доступен на всех вкусах *nix.
Я дополняю вывод большим количеством цифр (нулей), чем мне нужно, затем использую tail, чтобы использовать только то количество цифр, которое я ищу. Обратите внимание, что вы должны использовать '6' в хвосте, чтобы получить последние пять цифр:)
for i in $(seq 1 10)
do
RESULT=$(echo 00000$i | tail -c 6)
echo $RESULT
done
Если вы хотите N цифр, добавьте 10^N и удалите первую цифру.
for (( num=100; num<=105; num++ ))
do
echo ${num:1:3}
done
Выход:
01
02
03
04
05
Один из способов без использования разветвления внешнего процесса - это манипуляции со строками, в общем случае это будет выглядеть так:
#start value
CNT=1
for [whatever iterative loop, seq, cat, find...];do
# number of 0s is at least the amount of decimals needed, simple concatenation
TEMP="000000$CNT"
# for example 6 digits zero padded, get the last 6 character of the string
echo ${TEMP:(-6)}
# increment, if the for loop doesn't provide the number directly
TEMP=$(( TEMP + 1 ))
done
Это также хорошо работает на WSL, где разветвление - действительно сложная операция. У меня был список файлов 110000, используяprintf "%06d" $NUM
заняло более 1 минуты, решение, указанное выше, было выполнено примерно за 1 секунду.
Другой путь:
zeroos="000"
echo
for num in {99..105};do
echo ${zeroos:${#num}:${#zeroos}}${num}
done
Так что простая функция для преобразования любого числа будет:
function leading_zero(){
local num=$1
local zeroos=00000
echo ${zeroos:${#num}:${#zeroos}}${num}
}
Если вы сразу после заполнения чисел нулями для достижения фиксированной длины, просто добавьте ближайшее кратное 10, например. для 2 цифр добавьте 10^2, затем удалите первую 1 перед отображением вывода.
Это решение работает для заполнения / форматирования отдельных чисел любой длины или целой последовательности чисел с использованием цикла for.
# Padding 0s zeros:
# Pure bash without externals eg. awk, sed, seq, head, tail etc.
# works with echo, no need for printf
pad=100000 ;# 5 digit fixed
for i in {0..99999}; do ((j=pad+i))
echo ${j#?}
done
Проверено на Mac OSX 10.6.8, Bash ver 3.2.48
Это будет работать также:
for i in {0..9}{0..9}{0..9}{0..9}
do
echo "$i"
done
TL;DR
Ввод (Шаблон 1. Медленно):
$ seq 1 10 | xargs -n 1 printf "%05d\n"
Ввод (Шаблон 2. Быстрый):
$ seq 1 10 | awk '{printf("%05d\n", $1)}'
Вывод (один и тот же результат в каждом случае):
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
Объяснение
Я хотел бы предложить вышеуказанные модели. Эти реализации можно использовать как команду, чтобы мы могли легко использовать их снова. Все, о чем вам нужно заботиться в этих командах, — это длина чисел после преобразования (например, изменение числа
%05d
в
%09d
.) Кроме того, это также применимо к другим решениям, таким как следующие. Пример слишком зависит от моей среды, поэтому ваш вывод может отличаться, но я думаю, что вы можете легко определить его полезность.
$ wc -l * | awk '{printf("%05d\n", $1)}'
00007
00001
00001
00001
00013
00017
00001
00001
00001
00043
А вот так:
$ wc -l * | awk '{printf("%05d\n", $1)}' | sort | uniq
00001
00007
00013
00017
00043
Более того, если вы напишете таким образом, мы также сможем выполнять команды асинхронно. (Я нашел хорошую статью:https://www.dataart.com/en/blog/linux-pipes-tips-tricks)
отказ от ответственности: я не уверен в этом, и я не эксперт по *nix.
Тест производительности:
Супер медленно:
$ time seq 1 1000 | xargs -n 1 printf "%09d\n" > test
seq 1 1000 0.00s user 0.00s system 48% cpu 0.008 total
xargs -n 1 printf "%09d\n" > test 1.14s user 2.17s system 84% cpu 3.929 total
Относительно быстро:
for i in {1..1000}
do
printf "%09d\n" $i
done
$ time sh k.sh > test
sh k.sh > test 0.01s user 0.01s system 74% cpu 0.021 total
for i in {1..1000000}
do
printf "%09d\n" $i
done
$ time sh k.sh > test
sh k.sh > test 7.10s user 1.52s system 99% cpu 8.669 total
Быстро:
$ time seq 1 1000 | awk '{printf("%09d\n", $1)}' > test
seq 1 1000 0.00s user 0.00s system 47% cpu 0.008 total
awk '{printf("%09d\n", $1)}' > test 0.00s user 0.00s system 52% cpu 0.009 total
$ time seq 1 1000000 | awk '{printf("%09d\n", $1)}' > test
seq 1 1000000 0.27s user 0.00s system 28% cpu 0.927 total
awk '{printf("%09d\n", $1)}' > test 0.92s user 0.01s system 99% cpu 0.937 total
Если вам нужно реализовать решение с более высокой производительностью, возможно, для этого могут потребоваться другие методы, не использующие сценарий оболочки.
Объединениеseq -w
ответы с комментарием @tripleee к вопросу, вы можете сделать следующее:
seq -w 1 1000 | while read -r i; do
echo $i
done
Как объяснено в статье @tripleee linked, это будет считывать одно число из канала за раз, а не загружать всю последовательность в память сразу.
Обратите внимание, что я не вижу существенной разницы в скорости по сравнению с версией цикла for или{0000..1000}
версия.
Можно ли избежать зацикливания?
Если вы делаете это для запуска команды, которая может принимать несколько аргументов, может быть быстрее запустить команду напрямую, используя несколько аргументов и раскрытие фигурных скобок, чем цикл.
Например, в моей системе выполнение следующего цикла занимает 7 секунд:
for i in {0001..9001}; do; touch $i; done
Но следующее запускается почти мгновенно:
touch {0001..9001}
тебе не нужно
awk
для этого - либо
seq
или же
jot
достаточно только:
% seq -f '%05.f' 6 # bsd-seq
00001
00002
00003
00004
00005
00006
% gseq -f '%05.f' 6 # gnu-seq
00001
00002
00003
00004
00005
00006
% jot -w '%05.f' 6
00001
00002
00003
00004
00005
00006
…… если вы не собираетесь на большую территорию:
% gawk -Mbe '
function __(_,___) {
return +_<+___?___:_
}
BEGIN {
_+=_^=_<_
____="%0*.f\n"
} {
___=__($--_, !+$++_)
_____=__(++_+--_, length(______=+$NF))
do {
printf(____,_____,___)
} while (___++<______)
}' <<< '999999999999999999996 1000000000000000000003'
0999999999999999999996
0999999999999999999997
0999999999999999999998
0999999999999999999999
1000000000000000000000
1000000000000000000001
1000000000000000000002
1000000000000000000003
———————————————————————————————————————————————
Если вам нужно распечатать ОГРОМНЫЙ диапазон чисел, то этот подход может быть немного быстрее -
вывод каждого целого числа от 1 до 1 миллиона, дополненного левым нулем до 9 цифр в ширину, в
0.049s
* предостережение : у меня не было свободного времени, чтобы охватить все входные диапазоны :: это просто доказательство концепции, принимающей приращения степени 10
———————————————————————————————————————————————
( time ( LC_ALL=C mawk2 '
function jot(____,_______,_____,_,__,___,______) {
if(____==(____^!____)) {
return +____<+_______\
? sprintf("%0*.f",_______,____)\
: +____
}
_______= (_______-=____=length(____)-\
(_=!(_<_)))<+_ \
? "" \
: sprintf("%0*.f",_______,!_)
__=_= (!(__=_+=_+_))(__=(-+--_)+(__+=_)^++_)\
(__+=_=(((_--^_--+_++)^++_-_^!_)/_))(__+_)
_____= "."
gsub(_____,"\\&&",__)
____—-
do {
gsub(_____,__,_)
_____=_____"."
} while(—____)
gsub(_____,(_______)"&\n",_)
sub("^[^\n]+[\n]","",_)
sub(".$",""~"",_______)
return \
(_)(_______)\
sprintf("%0*.f",length(_____),__<__)
} { print jot($1,$2) }' <<< '10000000 9'
) | pvE9 ) |xxh128sum |ggXy3 | lgp3
sleep 2
( time ( LC_ALL=C jot 1000000 |
LC_ALL=C mawk2 '{ printf("%09.f\n", $1) }'
) | pvE9 ) |xxh128sum |ggXy3 | lgp3
out9: 9.54MiB 0:00:00 [ 275MiB/s] [ 275MiB/s] [<=> ]
( LC_ALL=C mawk2 <<< '1000000 9'; )
0.04s user 0.01s system 93% cpu 0.049 total
e0491043bdb4c8bc16769072f3b71f98 stdin
out9: 9.54MiB 0:00:00 [36.5MiB/s] [36.5MiB/s] [ <=> ]
( LC_ALL=C jot 1000000 | LC_ALL=C mawk2 '{printf("%09.f\n", $1)}'; )
0.43s user 0.01s system 158% cpu 0.275 total
e0491043bdb4c8bc16769072f3b71f98 stdin
К тому времени, когда вы сделаете 10 миллионов, разница во времени станет заметной:
out9: 95.4MiB 0:00:00 [ 216MiB/s] [ 216MiB/s] [<=> ]
( LC_ALL=C mawk2 <<< '10000000 9'; )
0.38s user 0.06s system 95% cpu 0.458 total
be3ed6c8e9ee947e5ba4ce51af753663 stdin
out9: 95.4MiB 0:00:02 [36.3MiB/s] [36.3MiB/s] [ <=> ]
( LC_ALL=C jot 10000000 | LC_ALL=C mawk2 '{printf("%09.f\n", $1)}'; )
4.30s user 0.04s system 164% cpu 2.638 total
be3ed6c8e9ee947e5ba4ce51af753663 stdin
out9: 95.4MiB 0:00:02 [35.2MiB/s] [35.2MiB/s] [ <=> ]
( LC_ALL=C python3 -c '__=1; ___=10**7;
[ print("{0:09d}".format(_)) for _ in range(__,___+__) ]'
) | pvE9 ) | xxh128sum |ggXy3 | lgp3 ; )
2.68s user 0.04s system 99% cpu 2.725 total
be3ed6c8e9ee947e5ba4ce51af753663 stdin
1.) Создайте последовательность чисел 'seq' от 1 до 1000 и зафиксируйте ширину '-w' (ширина определяется длиной конечного числа, в данном случае 4 цифры на 1000).
2.) Кроме того, выберите, какие цифры вы хотите, используя "sed -n" (в этом случае мы выбираем номера 1-100).
3.) "эхо" каждого номера. Числа хранятся в переменной "i", доступ к которой осуществляется с помощью "$".
Плюсы: этот код довольно чистый.
Минусы: 'seq' не свойственен всем системам Linux (насколько я понимаю)
for i in `seq -w 1 1000 | sed -n '1,100p'`;
do
echo $i;
done