Извлечение списка файлов и создание нового файла, содержащего этот список [Часть 2]

Ранее я задавал аналогичный вопрос

Извлечение списка файлов и создание нового файла, содержащего этот список

Однако на этот раз все сложнее:

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

По сути, папка имеет имена файлов в следующем формате:

1_Apple_A1_someword.txt 
1_Apple_A2_someword.txt
2_Apple_A1_someword.txt 
2_Apple_A2_someword.txt 
3_Apple_A1_someword.txt 
3_Apple_A2_someword.txt

и так далее до

1000_Apple_A1_someword.txt
1000_Apple_A2_someword.txt

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

1_Apple=1_Apple_A1_someword.txt 1_Apple_A2_someword.txt
2_Apple=2_Apple_A1_someword.txt 2_Apple_A2_someword.txt
3_Apple=3_Apple_A1_someword.txt 3_Apple_A2_someword.txt

и так далее... до

1000_Apple=1000_Apple_A1_someword.txt 1000_Apple_A2_someword.txt

Не могли бы вы сказать мне однострочную команду Unix, которая делает это? Возможно, используя «awk» и «sed»


person user1691717    schedule 02.10.2012    source источник


Ответы (6)


Это может сработать для вас (GNU sed):

sed '$!N;s/^\(\(.*\)_.*_.*\)\n/\2=\1 /' file
person potong    schedule 02.10.2012

Использование sed-скрипта:

#!/bin/sed -nf

: loop
H
s/\([^_]*_[^_]*\)_.*/\1/g

t clear_flag
: clear_flag

$! {
    N
    s/^\([^_]*_[^\n]*\)\n\(\1[^\n]*\)$/\2/
    t loop
}

x
s/^\n//
s/\([^_]*_[^_]*\)_/\1=\1_/
s/\n/ /gp

s/.*//
x
D

Я постараюсь все объяснить. Во-первых, у нас есть цикл для объединения всех файлов, начинающихся с одного и того же префикса. Я определил префикс на основе ваших примеров, и он определяется как строка, заканчивающаяся вторым символом подчеркивания. Цикл определяется меткой с помощью команды «:». Здесь мы обозначили нашу петлю как «петля». Далее, при необходимости, мы «прыгаем» обратно к началу цикла с помощью тестовой команды «t».

Первая команда — добавить строку в Hold-space (вспомогательный буфер). Строка имеет префикс новой строки ('\n') автоматически с помощью sed, прежде чем она будет добавлена.

Вторая команда извлекает префикс. Мы делаем это, захватывая последовательность символов, не являющихся символами подчеркивания ([^_]*), затем символ подчеркивания, а затем другие символы, не являющиеся символами подчеркивания. Поскольку этот шаблон находится между скобками с обратной косой чертой (\( и \)), sed захватит ввод, соответствующий этому шаблону, и сохранит его во вспомогательной переменной с именем \1 (поскольку это первый захват в этой строке). Затем мы пропускаем символ подчеркивания, за которым следует последовательность любых символов. Замена — это то, что мы захватили, поэтому на самом деле мы просто удалили все после второго подчеркивания, включая его.

Теперь мы используем обходной путь для очистки внутреннего флага seds, указывающего, произошла ли успешная замена с момента последней команды «t» или с момента запуска скрипта. Тестовая команда ("t") разветвится (перейдет) к метке, если команда подстановки завершится успешно, а затем очистит внутренний флаг. Это необходимо для нашей второй команды «t» ниже. В случае успеха или неудачи (т. е. если он разветвляется или нет), он все равно продолжит выполнение после метки «clear_flag».

Теперь мы используем команду «{», чтобы запустить группу команд. Однако у нас есть префикс адреса перед ним, который sed использует, чтобы определить, следует ли запускать эти команды или нет. В нашем случае группа выполняется только в том случае, если последняя прочитанная строка ввода не была последней строкой (символ доллара «$» представляет последнюю строку ввода, а «!» представляет отрицание).

Первая команда в группе добавит следующую строку из ввода в текущее пространство шаблонов (т.е. рабочий буфер). Предыдущая строка и новая строка разделяются символом новой строки (\n).

Третья команда проверит, начинается ли новая прочитанная строка с нашего префикса, и удалит изолированный префикс (т.е. предыдущую строку). Поскольку мы удалили второе подчеркивание из префикса, который мы оставили в предыдущей строке, и поскольку мы добавили новую строку, изолированный префикс теперь заканчивается перед символом новой строки. Поэтому захваченный шаблон теперь читает символы, которые не являются новой строкой ([^\n]*) после подчеркивания. После того, как мы захватили изолированный префикс, мы пропускаем символ новой строки, разделяющий предыдущую и новую строку, затем начинаем другой захват (который будет сохранен в \2, потому что это второй захват в этой строке). Этот захват будет (надеюсь) соответствовать второй строке. Надеюсь, потому что мы требуем, чтобы совпадение начиналось точно так же, как то, что было сопоставлено в первом захвате (поэтому первая вещь во втором захвате — это обратная ссылка на первый захват, т. е. \1). После этого мы сопоставляем последовательность символов, не являющихся символами новой строки, и после второго захвата ожидаем конец строки.

Если эта последняя команда подстановки успешна, мы обнаружили, что вновь прочитанная строка также имеет тот же префикс, поэтому теперь мы должны вернуться к началу цикла. Это функция команды "t". Он проверит, были ли выполнены какие-либо команды подстановки после последней команды «t», и если да, то перейдет к заданной метке. В нашем случае мы переходим (прыгаем) обратно к метке «loop». Теперь мы можем понять, почему нам понадобился предыдущий обходной путь «t». Без него первая подстановочная команда может завершиться успешно, в то время как та, которая нас действительно интересует, может завершиться ошибкой, а «t» все равно вернется к метке «loop».

Если он выходит из цикла, это означает, что новая прочитанная строка не имеет того же префикса. Поэтому теперь мы можем напечатать то, что было сопоставлено ранее.

Начнем с того, что заменим содержимое пространства шаблонов на содержимое пространства хранения с помощью команды exchange ("x"). Теперь наше пространство шаблонов содержит все файлы с одинаковым префиксом, а наше пространство хранения содержит текущий префикс в изолированной строке, а затем строку с первым файлом, который не использует тот же самый префикс.

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

Теперь нам нужно сгенерировать формат задания. Вот почему у нас есть знакомая команда замены, мы снова извлекаем префикс, за исключением того, что теперь мы удалили .*, чтобы сохранить остальную часть строки нетронутой. Замена включает в себя префикс (захвачено), знак равенства, а также восстанавливаем то, что мы удалили из первого файла в пространстве шаблона: его префикс и его подчеркивание.

Мы почти готовы распечатать строку, но имена файлов все еще разделены символами новой строки. Поэтому мы заменяем все новые строки (флаг g указывает sed повторять команду замены в строке ввода столько раз, сколько это возможно) пробелами. Поскольку теперь строка готова, мы можем добавить префикс p, чтобы указать sed напечатать ее.

Последние шаги — подготовка к повторному запуску скрипта для следующего префикса. Пространство хранения должно быть пустым, чтобы его можно было использовать для хранения имен файлов с новым префиксом. Вот у нас есть команда заменить каждый символ в пространстве шаблона ничем, за которым следует команда обмена.

Трюмное пространство готово. Теперь мы должны подготовить пространство для шаблона. Он должен содержать только первую строку имени файла с новым префиксом. Чтобы оказаться в этом состоянии, все, что нам нужно сделать, это удалить старый префикс, который хранится в первой строке. Мы могли бы сделать что-то вроде s/.*\n//, чтобы заменить все символы, кроме символов последней строки (которая содержит имя файла с новым префиксом), но команда D сделает это и заставит скрипт начать выполнение снова, не читая другую строку, так что это избавляет нас от необходимости печатать.

Хотя сценарий может быть немного загадочным, а описание громоздким, как только вы поймете, что происходит, все станет просто(r) =)

Что-то, что необходимо упомянуть: входные данные должны быть отсортированы (или, по крайней мере, файлы с одинаковыми префиксами должны быть сгруппированы вместе).

Надеюсь это поможет!

person Janito Vaqueiro Ferreira Filho    schedule 02.10.2012
comment
Кажется интересным (я изучу ваш сценарий), но очень сложно для простой задачи. Зачем использовать такое орудие пыток (sed) для этого? Это своего рода искусство, как известный pacman или тетрис в sed? Просто интересно. - person Gilles Quenot; 03.10.2012
comment
Я действительно не знаю, но я думаю, что проверяю пределы sed =) - person Janito Vaqueiro Ferreira Filho; 03.10.2012

Используя Перл:

perl -pe 'if ($. % 2) { /([0-9]+_Apple)/ and print "$1="; s/\s+$/ /; }'

В нечетных строках сопоставьте ...Apple, выведите его с помощью = и замените пробел в конце строки одним пробелом.

Примечание. Переменные Unix не могут иметь имена, начинающиеся с цифр.

person choroba    schedule 02.10.2012

Используя короткий однострочный awk:

awk -F'_' '{if (NR % 2) {printf("%s_%s=%s", $1, $2, $0)} else {print}}' FILE
person Gilles Quenot    schedule 02.10.2012

Использование СЭД:

sed 'N;s/\n/ /;s/\([^_]*_Apple\)/\1=\1/'
person Beta    schedule 02.10.2012

num=1
while [ $num -le 1000 ]
do
echo "${num}_Apple=${num}_Apple_A1_someword.txt ${num}_Apple_A2_somword.txt"
num=`expr $num + 1`
done

Выход:

1_Apple=1_Apple_A1_someword.txt 1_Apple_A2_somword.txt
2_Apple=2_Apple_A1_someword.txt 2_Apple_A2_somword.txt
3_Apple=3_Apple_A1_someword.txt 3_Apple_A2_somword.txt
4_Apple=4_Apple_A1_someword.txt 4_Apple_A2_somword.txt
5_Apple=5_Apple_A1_someword.txt 5_Apple_A2_somword.txt
...........

Если число 1000 не является статическим, вы можете получить значение из самого файла как:

num=`cat file|sort|tail -1|awk -F"_" '{print $1}'

Спасибо

person par181    schedule 03.10.2012