Как обнулить последовательность целых чисел в 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 с "%05d", например

printf "%05d" 1

Очень простое использование 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
Другие вопросы по тегам