Вот два взаимоисключающих цикла sed
:
sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' <<""
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
1972 12 31 999
1972 1 1 933
1972 1 2 837
...
1972 12 31 343
YEAR MONTH DAY RES
1971 1 1 245
1971 1 2 587
...
1971 12 31 685
1972 1 1 549
1972 1 2 746
...
1972 12 31 999
В основном sed
имеет два состояния - p
rint и eat. В первом состоянии - состоянии p
rint - sed
автоматически p
rints каждую входную строку, затем проверяет ее на соответствие шаблону / 12 * 31 /
. Если текущее место шаблона !
не совпадает, он d
удаляется и sed
втягивает следующую строку ввода и начинает скрипт заново сверху - с команды p
rint, не пытаясь выполнить ничего, что следует за командой d
elete.
Когда строка ввода does соответствует / 12 * 31 /
, однако, sed
переходит ко второй половине сценария - циклу eat. Сначала он определяет ветвь :
метки с именем n
; затем перезаписывает текущее пространство шаблона входной строкой n
ext, а затем сравнивает текущее пространство шаблона с //
последнего совпавшего шаблона. Поскольку предыдущая совпавшая строка была перезаписана строкой n
ext, первая итерация этого цикла eat не совпадает, и каждый раз !
не sed
b
возвращается к метке :n
, чтобы получить входную строку n
ext и снова сравнить ее с //
последним совпавшим шаблоном.
Когда, наконец, будет найдено еще одно совпадение - примерно 365 n
ext строк спустя - sed
автоматически -n
ot напечатает его, когда завершит свой сценарий, втянет следующую входную строку и снова начнет сверху с команды p
rint в своем первом состоянии. Таким образом, каждое состояние цикла будет переходить в следующее на том же ключе и делать как можно меньше действий для поиска следующего ключа.
Обратите внимание, что весь сценарий завершается без вызова ни одной процедуры редактирования, и что он требует только компиляции единственного regexp. Получившийся автомат очень прост - он понимает только
и [^123 ]
. Более того, по крайней мере половина сравнений, скорее всего, будет сделана без компиляций, потому что единственный адрес, на который ссылается цикл eat, вообще пустой //
. Поэтому sed
может полностью завершить этот цикл одним вызовом regexec()
на каждую строку ввода. sed
may делает подобное и для цикла p
rint.
Мне было интересно, как могут работать различные ответы здесь, и поэтому я придумал свою собственную таблицу:
dash <<""
d=0 D=31 IFS=: set 1970 1
while case "$*:${d#$D}" in (*[!:]) ;;
($(($1^($1%4)|(d=0))):1:)
D=29 set $1 2;;
(*:1:) D=28 set $1 2;;
(*[3580]:)
D=30 set $1 $(($2+1));;
(*:) D=31 set $(($1+!(t<730||(t=0)))) $(($2%12+1))
esac
do printf '%-6d%-4d%-4d%d\n' "$@" $((d+=1)) $((t+=1))
done| head -n1000054 >/tmp/dates
dash <<<'' 6.62s user 6.95s system 166% cpu 8.156 total
Это помещает миллион с лишним строк в /tmp/dates
и удваивает вывод для каждого из годов 1970 - 3338. Файл выглядит так:
tail -n1465 </tmp/dates | head; echo; tail </tmp/dates
3336 12 27 728
3336 12 28 729
3336 12 29 730
3336 12 30 731
3336 12 31 732
3337 1 1 1
3337 1 2 2
3337 1 3 3
3337 1 4 4
3337 1 5 5
3338 12 22 721
3338 12 23 722
3338 12 24 723
3338 12 25 724
3338 12 26 725
3338 12 27 726
3338 12 28 727
3338 12 29 728
3338 12 30 729
3338 12 31 730
... во всяком случае, часть его.
Затем я попробовал использовать различные команды:
for cmd in "sort -uVk1,3" \
"sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn'" \
"awk '"'{u=$1 $2 $3 $4;if (!a[u]++) print;}'\'
do eval "time ($cmd|wc -l)" </tmp/dates
done
500027
( sort -uVk1,3 | wc -l; ) \
1.85s user 0.11s system 280% cpu 0.698 total
500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.64s user 0.09s system 110% cpu 0.659 total
500027
( awk '{u=$1 $2 $3 $4;if (!a[u]++) print;}' | wc -l; ) \
1.46s user 0.15s system 104% cpu 1.536 total
Команды sort
и sed
выполнились менее чем за половину времени awk
- и эти результаты были типичными. Я выполнил их несколько раз. Похоже, что все команды также выписывают правильное количество строк - и поэтому, вероятно, все они работают.
sort
и sed
были довольно хороши - с sed
в целом на волосок впереди - по времени завершения для каждого запуска, но sort
делает больше фактической работы для достижения своих результатов, чем любая из двух других команд. Она выполняет параллельные задания для выполнения своей задачи и получает большую пользу от моего многоядерного процессора. awk
и sed
оба используют одноядерный процессор в течение всего времени обработки.
Здесь приведены результаты стандартного, современного GNU sed
, но я пробовал и другой. На самом деле, я пробовал все три команды с другими двоичными файлами, но только команда sed
действительно работала с моими инструментами heirloom. Остальные, как я полагаю, из-за нестандартного синтаксиса, просто вылетали с ошибкой, не успев начать работу.
Хорошо использовать стандартный синтаксис, когда это возможно - так вы можете свободно использовать более простые, отточенные и эффективные реализации во многих случаях:
PATH=/usr/heirloom/bin/posix2001:$PATH; time ...
500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.31s user 0.12s system 136% cpu 0.318 total
Con tu enfoque tienes que usar join
dos veces (o cambiar tu enfoque para hacerlo con una sola join
invocación):
file1
conjoin -t'|' -e0 -a1 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)
file2
conjoin -t'|' -e0 -v2 -o 2.2,2.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)
Puede hacer lo mismo con una sola invocación awk
, almacenando $4
en dos matrices indexadas por, p. $1|$2
y luego en el bloque END
iterando sobre cada índice de matriz, comparándolos e imprimiendo en consecuencia:
awk -F'|' 'NR==FNR{z[$1"|"$2]=$4;next}{x[$1"|"$2]=$4}
END{for (j in x){if (!(j in z)){print j, "0", x[j]}};
for (i in z){if (i in x){print i, z[i], x[i]} else {print i, z[i], "0"}}
}' OFS="|" file1 file2
Otro awk
enfoque:
awk -F'|' 'NR==FNR{f1[$1FS$2]=$NF;next} {f2[$1FS$2]=$NF}
END{for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]};
for (y in f2) print y, 0, f2[y]
}' file[12] OFS='|'
NR==FNR{f1[$1FS$2]=$NF;next}
, esto se ejecutará solo para el archivo 1 y con la combinación de teclas de $1FS$2
almacenará el valor de la última columna $NF
en una matriz llamadaf1
(FS
que se sustituirá con |
como el campo F de awk S operador ). {f2[$1FS$2]=$NF}
, igual que arriba pero esto se ejecutará solo para el archivo 2 for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]}
, haga un bucle dentro de la matriz f1
e imprima la clave (x
), su valor en el archivo 1 f1[x]
y si hay la misma clave del archivo 1 en el archivo 2, imprímala también, de lo contrario imprima0
(Condición ternaria utilizadaf2[x]?f2[x]:0
), después de eso también estamos eliminando el registro de la misma clave del archivo2 con delete f2[x]
. for (y in f2) print y, 0, f2[y]
, ahora la matriz f2
tiene registros que existen solo en el archivo2, por lo que estamos imprimiendo su clave (y
), 0
porque no existe en el archivo1 y su valor en el archivo2 f2[y]
. Lo siguiente reemplaza el primer |
en ambos archivos con@
(usa un carácter que no aparece en ninguna otra parte del archivo ), realiza el join
y luego cambia el @
de vuelta al original |
. De esta forma, creamos un nuevo campo de combinación delimitado|
-que consta de las columnas 1 y 2 de los archivos originales.
join -t'|' -e0 -a1 -a2 -o0,1.3,2.3 \
<( sed 's/|/@/' file1 | sort ) \
<( sed 's/|/@/' file2 | sort ) |
tr '@' '|'
En la especificación del campo de salida (-o
), un cero representa el campo de combinación y la columna 3 en cualquiera de los archivos es en realidad la columna 4 de los datos originales.
Para los archivos de entrada dados, esto genera
1111|AAA|50|10
1111|BBB|30|0
1111|CCC|0|20
2222|BBB|10|0
3333|AAA|0|40