Вот простая строка моего использования:
grep -i '"location_country":"country name"' file.txt >> sample.txt
Я выполняю поиск в большом файле с несколькими странами и хотел бы динамически создать текстовый файл с названием страны и сохранить все совпадения из этой страны в этом файле, то есть country name.txt
для каждого случая.
что-то вроде этого
grep -i '"location_country":"(.+)"' file.txt >> \1.txt
Пример данных:
{"full_name":"name1","location_country":"united kingdom"}
{"full_name":"name2","location_country":"united states"}
{"full_name":"name3","location_country":"china"}
Поэтому мне нужно создать 3 отдельных текстовых файла с названиями стран, например, united kingdom.txt
содержащие:
{"full_name":"name1","location_country":"united kingdom"}
Я уже использую скрипты bash, поэтому меня это не волнует. Как мне этого добиться? Я использую машину Linux.
решение1
Ваш файл состоит из набора объектов JSON. Каждый объект содержит ключ .location_country
. Из каждого объекта мы можем создать команду оболочки, которая записывает сериализованную копию самого объекта в файл, названный по значению ключа .location_country
. Эти команды оболочки затем могут быть выполнены оболочкой.
С использованием jq
,
jq -r '"printf \"%s\\n\" \(. | @json | @sh) >\(.location_country|@sh).txt"' file.txt
Сериализованный объект может быть создан с помощью @json
оператора в jq
, который выдаст закодированную в JSON строку, содержащую входной документ, в данном случае текущий объект. Затем это передается в , @sh
чтобы правильно заключить строку в кавычки для оболочки. @sh
Оператор также используется для создания части имени выходного файла из значения ключа .location_country
.
По сути, команда создает код оболочки, который будет вызывать printf
, выводя текущий объект и перенаправляя вывод в определенный файл.
Учитывая ваш пример данных в file.txt
, это выдаст следующее:
printf "%s\n" '{"full_name":"name1","location_country":"united kingdom"}' >'united kingdom'.txt
printf "%s\n" '{"full_name":"name2","location_country":"united states"}' >'united states'.txt
printf "%s\n" '{"full_name":"name3","location_country":"china"}' >'china'.txt
Вы можете перенаправить это в отдельный файл и запустить его для sh
выполнения команд, или вы можете использовать его eval
непосредственно в оболочке:
eval "$( jq ...as above... )"
Поскольку мы используем правильный парсер JSON, jq
приведенный выше код будет работать, даже если входной документ JSON не отформатирован с одним объектом на строку.
$ cat file.txt
{
"full_name": "name1",
"location_country": "united kingdom"
}
{
"full_name": "name2",
"location_country": "united states"
}
{
"full_name": "name3",
"location_country": "china"
}
$ jq -r '"printf \"%s\\n\" \(. | @json | @sh) >\(.location_country|@sh).txt"' file.txt
printf "%s\n" '{"full_name":"name1","location_country":"united kingdom"}' >'united kingdom'.txt
printf "%s\n" '{"full_name":"name2","location_country":"united states"}' >'united states'.txt
printf "%s\n" '{"full_name":"name3","location_country":"china"}' >'china'.txt
$ eval "$( jq -r '"printf \"%s\\n\" \(. | @json | @sh) >\(.location_country|@sh).txt"' file.txt )"
$ ls
china.txt file.txt united kingdom.txt united states.txt
$ cat 'united kingdom.txt'
{"full_name":"name1","location_country":"united kingdom"}
решение2
С использованиемawk
Вход
$ cat input_file
{"full_name":"name1","location_country":"united kingdom"}
{"full_name":"name2","location_country":"united states"}
{"full_name":"name3","location_country":"china"}
{"full name":"name12","location":"china"}
{"full name":"name11","location":"china"}
awk -F"[\"|:]" '$10~/[A-Za-z]/ {print > $10".txt"}' input_file
Выход
$ cat china.txt
{"full_name":"name3","location_country":"china"}
{"full name":"name12","location":"china"}
{"full name":"name11","location":"china"}
$ cat united\ kingdom.txt
{"full_name":"name1","location_country":"united kingdom"}
$ cat united\ states.txt
{"full_name":"name2","location_country":"united states"}
решение3
Учитывая ваши комментарии ниже, это должно сделать то, что вам нужно, используя GNU awk для третьего аргумента match() и обработки множества одновременно открытых файлов:
awk 'match($0,/"location_country":"([^"]+)"/,a) { print > (a[1] ".txt") }' file
Для скорости выполнения, вероятно, лучшим подходом будет декорирование/сортировка/использование/декорирование, например:
awk -v OFS='"' 'match($0,/"location_country":"[^"]+"/) { print substr($0,RSTART+20,RLENGTH-21), $0 }' file |
sort -t'"' -k1,1 |
awk -F'"' '$1!=prev { close(out); out=$1 ".txt"; prev=$1 } { print > out }' |
cut -d'"' -f2-
и это будет работать с любым сортом, awk и cut.
Оригинальный ответ:
Если ваши данные всегда такие простые/регулярные, то все, что вам нужно, это сделать с помощью GNU awk (для обработки множества одновременно открытых выходных файлов):
awk -F'"' '{ print > ($5 ".txt") }' file
или это с любым awk:
awk -F'"' '{
out = $5 ".txt"
if ( !seen[out]++ ) {
printf "" > out
}
print >> out
close(out)
}' file
Вышеуказанный способ будет работать независимо от размера входного файла, если на диске достаточно места для создания выходных файлов.
Если вы хотите, вы можете сделать это более эффективно, сначала отсортировав данные по названию страны:
sort -t'"' -k5,5 file |
awk -F'"' '$5 != prev{ close(out); out=$5 ".txt"; prev=$5 } { print > out }'
Последний скрипт будет работать с любой сортировкой и любым awk, но он может переставить порядок входных строк для каждой страны. Если вас это волнует и у вас есть сортировка GNU, то добавьте аргумент -s
. Если вас это волнует и у вас нет сортировки GNU, дайте мне знать, так как есть довольно простое решение.