Инструмент командной строки для «cat» попарно расширяет все строки в файле

Предположим, у меня есть файл (назовите его sample.txt), который выглядит так:

Row1,10 Row2,20 Row3,30 Row4,40 

Я хочу, чтобы иметь возможность работать с потоком из этого файла, который по существу является парной комбинацией всех четырех строк (так что мы должны в итоге получить 16 в общей сложности). Например, я ищу команду потоковой передачи (то есть эффективную), где вывод:

 Row1,10 Row1,10 Row1,10 Row2,20 Row1,10 Row3,30 Row1,10 Row4,40 Row2,20 Row1,10 Row1,20 Row2,20 ... Row4,40 Row4,40 

Моим вариантом использования является то, что я хочу передать этот вывод в другую команду (например, awk), чтобы вычислить некоторую метрику об этой парной комбинации.

У меня есть способ сделать это в awk, но я беспокоюсь, что мое использование блока END {} означает, что я в основном сохраняю весь файл в памяти до вывода. Пример кода:

 awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt Row3,30 Row3,30 Row3,30 Row4,40 Row3,30 Row1,10 Row3,30 Row2,20 Row4,40 Row3,30 Row4,40 Row4,40 Row4,40 Row1,10 Row4,40 Row2,20 Row1,10 Row3,30 Row1,10 Row4,40 Row1,10 Row1,10 Row1,10 Row2,20 Row2,20 Row3,30 Row2,20 Row4,40 Row2,20 Row1,10 Row2,20 Row2,20 

Есть ли эффективный потоковый способ сделать это без существенного хранения файла в памяти, а затем вывода в блоке END?

  • считывать ввод от пользователя во время цикла, имеющего перенаправление ввода и вывода
  • Удалить строки из файла в зависимости от строк, найденных в другом файле
  • Как запустить команду только после того, как предыдущая команда не удалась в bash?
  • Shell Script не может управлять командой man
  • Unix - как извлекать файлы на основе части имени файла, которая является датой
  • Что такое sourcing мой сценарий оболочки?
  • Как написать скрипт для выполнения файлов в нескольких каталогах
  • как удалить ssh предупреждение в сценарии оболочки
  • 9 Solutions collect form web for “Инструмент командной строки для «cat» попарно расширяет все строки в файле”

    Вот как это сделать в awk, чтобы не хранить весь файл в массиве. Это в основном тот же алгоритм, что и terdon's.

    Если вам нравится, вы можете даже дать ему несколько файлов в командной строке, и он будет обрабатывать каждый файл независимо, объединяя результаты вместе.

     #!/usr/bin/awk -f #Cartesian product of records { file = FILENAME while ((getline line <file) > 0) print $0, line close(file) } 

    В моей системе это работает примерно через 2/3 времени для решения perl terdon.

    Я не уверен, что это лучше, чем делать это в памяти, но с седлом, который выводит его infile для каждой линии в своем infile, а другой на другой стороне трубы, чередуя H старое пространство со строками ввода …

     cat <<\IN >/tmp/tmp Row1,10 Row2,20 Row3,30 Row4,40 IN </tmp/tmp sed -e 'i\ ' -e 'r /tmp/tmp' | sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D' 

    ВЫВОД

     Row1,10 Row1,10 Row1,10 Row2,20 Row1,10 Row3,30 Row1,10 Row4,40 Row2,20 Row1,10 Row2,20 Row2,20 Row2,20 Row3,30 Row2,20 Row4,40 Row3,30 Row1,10 Row3,30 Row2,20 Row3,30 Row3,30 Row3,30 Row4,40 Row4,40 Row1,10 Row4,40 Row2,20 Row4,40 Row3,30 Row4,40 Row4,40 

    Я сделал это по-другому. Он хранит некоторые в памяти – он хранит строку, такую ​​как:

     "$1" - 

    … для каждой строки в файле.

     pairs(){ [ -e "$1" ] || return set -- "$1" "$(IFS=0 n= case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))" eval "cat -- $2 </dev/null | paste -d ' \n' -- $2" } 

    Это очень быстро. Это файл cat столько раз, сколько строк в файле в |pipe . На другой стороне трубы этот вход сливается с самим файлом столько раз, сколько строк в файле.

    Материал case предназначен только для переносимости – yash и zsh добавляют один элемент к расколу, тогда как mksh и posh оба теряют один. ksh , dash , busybox и bash все разбиты на столько же полей, сколько и нулей, напечатанных printf . Как написано выше, эти результаты одинаковы для каждой из вышеперечисленных оболочек на моей машине.

    Если файл очень длинный, могут $ARGMAX проблемы с $ARGMAX с слишком большим количеством аргументов, и в этом случае вам нужно будет ввести xargs или подобное.

    Учитывая тот же ввод, который я использовал до того, как выход идентичен. Но, если бы я пошел больше …

     seq 10 10 10000 | nl -s, >/tmp/tmp 

    Это генерирует файл, почти идентичный тому, что я использовал раньше (sans 'Row'), но на 1000 строк. Вы сами можете убедиться, насколько это быстро:

     time pairs /tmp/tmp |wc -l 1000000 pairs /tmp/tmp 0.20s user 0.07s system 110% cpu 0.239 total wc -l 0.05s user 0.03s system 32% cpu 0.238 total 

    На 1000 строк есть некоторые незначительные изменения в производительности между оболочками – bash неизменно является самым медленным, но поскольку единственная работа, которую они выполняют в любом случае, генерирует строку arg (1000 копий имени filename - ), эффект минимален. Разница в производительности между zsh – как указано выше – и bash составляет 100 секунды.

    Вот еще одна версия, которая должна работать для файла любой длины:

     pairs2()( [ -e "$1" ] || exit rpt() until [ "$((n+=1))" -gt "$1" ] do printf %s\\n "$2" done [ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit : & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")" ln -s "$PWD/${1##*/}" "$2" || exit n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0 n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3 }; rm "$2" ) 

    Он создает soft-link для своего первого arg в /tmp с полуслучайным именем, чтобы он не зависел от странных имен файлов. Это важно, потому что аргументы cat подаются к нему через трубу через xargs . вывод cat сохраняется в <&3 то время как sed p обрабатывает каждую строку в первом arg столько раз, сколько строк в этом файле – и его скрипт также передается ему через канал. Снова paste объединяет свой вход, но на этот раз он принимает только два аргумента - снова для своего стандартного ввода и имени ссылки /dev/fd/3 .

    Это последнее – ссылка /dev/fd/[num] – должно работать на любой Linux-системе и многом другом, но если он не создает именованный канал с mkfifo и использование этого вместо этого должно работать.

    Последнее, что он делает, это rm софт-ссылка, которую он создает перед выходом.

    Эта версия на самом деле быстрее работает в моей системе. Я предполагаю, что это происходит потому, что, хотя он запускает больше приложений, он начинает раздавать им свои аргументы немедленно, а перед тем, как он сначала складывает их.

     time pairs2 /tmp/tmp | wc -l 1000000 pairs2 /tmp/tmp 0.30s user 0.09s system 178% cpu 0.218 total wc -l 0.03s user 0.02s system 26% cpu 0.218 total 

    Ну, вы всегда можете сделать это в своей оболочке:

     while read i; do while read k; do echo "$i $k"; done < sample.txt done < sample.txt 

    Это намного медленнее, чем ваше awk решение (на моей машине потребовалось ~ 11 секунд для 1000 строк, против ~ 0,3 секунды в awk ), но по крайней мере в нем никогда не было больше пары строк в памяти.

    Цикл выше работает для очень простых данных, которые у вас есть в вашем примере. Он будет задыхаться от косых черт, и он будет есть конечные и ведущие пространства. Более надежная версия того же:

     while IFS= read -ri; do while IFS= read -rk; do printf "%s %s\n" "$i" "$k"; done < sample.txt done < sample.txt 

    Другой выбор – использовать perl вместо этого:

     perl -lne '$line1=$_; open(A,"sample.txt"); while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt 

    Сценарий выше будет читать каждую строку входного файла ( -ln ), сохранить его как $l , снова открыть sample.txt и распечатать каждую строку вместе с $l . Результатом являются все парные комбинации, в то время как только 2 строки хранятся в памяти. В моей системе это заняло всего около 0.6 секунды на 1000 строк.

    С zsh :

     a=( Row1,10 Row2,20 Row3,30 Row4,40 ) printf '%s\n' $^a' '$^a 

    $^a на массиве включает {elt1,elt2} расширение (например, в {elt1,elt2} ) для массива.

    Вы можете скомпилировать этот код c ++ для получения довольно быстрых результатов.
    Он заканчивается около 0.19 – 0.27 секунд в файле с 1000 строками.

    В настоящее время он считывает 10000 строк в память (чтобы ускорить печать на экране), которые, если бы у вас было 1000 символов в строке, использовалось бы менее 10 10mb памяти, которое я бы не думал, было бы проблемой. Вы можете полностью удалить этот раздел и просто распечатать непосредственно на экране, если это действительно вызывает проблему.

    Вы можете скомпилировать с помощью g++ -o "NAME" "NAME.cpp"
    Где NAME – это имя файла для сохранения, а NAME.cpp – это файл, этот код сохраняется в

    CTEST.cpp:

     #include <iostream> #include <string> #include <fstream> #include <iomanip> #include <cstdlib> #include <sstream> int main(int argc,char *argv[]) { if(argc != 2) { printf("You must provide at least one argument\n"); // Make sure only one arg exit(0); } std::ifstream file(argv[1]),file2(argv[1]); std::string line,line2; std::stringstream ss; int x=0; while (file.good()){ file2.clear(); file2.seekg (0, file2.beg); getline(file, line); if(file.good()){ while ( file2.good() ){ getline(file2, line2); if(file2.good()) ss << line <<" "<<line2 << "\n"; x++; if(x==10000){ std::cout << ss.rdbuf(); ss.clear(); ss.str(std::string()); } } } } std::cout << ss.rdbuf(); ss.clear(); ss.str(std::string()); } 

    демонстрация

     $ g++ -o "Stream.exe" "CTEST.cpp" $ seq 10 10 10000 | nl -s, > testfile $ time ./Stream.exe testfile | wc -l 1000000 real 0m0.243s user 0m0.210s sys 0m0.033s 

    Один из вариантов с Python состоит в том, чтобы сопоставить карту с файлом и воспользоваться тем фактом, что библиотека регулярных выражений Python может работать непосредственно с файлами с отображением памяти. Хотя это имеет вид запущенных вложенных циклов над файлом, сопоставление памяти гарантирует, что ОС обеспечивает оптимальную доступность физической памяти в игре

     import mmap import re with open('test.file', 'rt') as f1, open('test.file') as f2: with mmap.mmap(f1.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m1,\ mmap.mmap(f2.fileno(), 0, flags=mmap.MAP_SHARED, access=mmap.ACCESS_READ) as m2: for line1 in re.finditer(b'.*?\n', m1): for line2 in re.finditer(b'.*?\n', m2): print('{} {}'.format(line1.group().decode().rstrip(), line2.group().decode().rstrip())) m2.seek(0) 

    Альтернативно быстрое решение на Python, хотя эффективность памяти может все еще быть проблемой

     from itertools import product with open('test.file') as f: for a, b in product(f, repeat=2): print('{} {}'.format(a.rstrip(), b.rstrip())) Row1,10 Row1,10 Row1,10 Row2,20 Row1,10 Row3,30 Row1,10 Row4,40 Row2,20 Row1,10 Row2,20 Row2,20 Row2,20 Row3,30 Row2,20 Row4,40 Row3,30 Row1,10 Row3,30 Row2,20 Row3,30 Row3,30 Row3,30 Row4,40 Row4,40 Row1,10 Row4,40 Row2,20 Row4,40 Row3,30 Row4,40 Row4,40 
     join -j 2 file.txt file.txt | cut -c 2- 
    • соединить не существующее поле и удалить первое пространство

    Поле 2 пусто и равно для всего элемента file.txt, поэтому join объединяет каждый элемент со всеми остальными: он фактически вычисляет декартово произведение.

    В bash ksh также должен работать, используя только встроенные оболочки:

     #!/bin/bash # we require array support d=( $(< sample.txt) ) # quote arguments and # build up brace expansion string d=$(printf -- '%q,' "${d[@]}") d=$(printf -- '%s' "{${d%,}}' '{${d%,}}") eval printf -- '%s\\n' "$d" 

    Обратите внимание, что хотя в этом файле содержится весь файл в памяти в переменной оболочки, ему нужен только один доступ для чтения.

    sed решение.

     line_num=$(wc -l < input.txt) sed 'r input.txt' input.txt | sed -re "1~$((line_num + 1)){h;d}" -e 'G;s/(.*)\n(.*)/\2 \1/' 

    Объяснение:

    • sed 'r file2' file1 – прочитать все содержимое файла file2 для каждой строки файла1.
    • Конструкция 1~i означает 1-ю строку, затем 1 + i линию, 1 + 2 * i, 1 + 3 * i и т. Д. Поэтому 1~$((line_num + 1)){h;d} означает h old заостренную линию в буфер, d elete pattern space и запуск нового цикла.
    • 'G;s/(.*)\n(.*)/\2 \1/' – для всех строк, кроме выбранных на предыдущем шаге, выполните следующее: G et line из буфера удержания и добавьте его в текущую строку , Затем поменяйте местами линий. Был current_line\nbuffer_line\n , стал buffer_line\ncurrent_line\n

    Вывод

     Row1,10 Row1,10 Row1,10 Row2,20 Row1,10 Row3,30 Row1,10 Row4,40 Row2,20 Row1,10 Row2,20 Row2,20 Row2,20 Row3,30 Row2,20 Row4,40 Row3,30 Row1,10 Row3,30 Row2,20 Row3,30 Row3,30 Row3,30 Row4,40 Row4,40 Row1,10 Row4,40 Row2,20 Row4,40 Row3,30 Row4,40 Row4,40 
    Linux и Unix - лучшая ОС в мире.