У меня есть несколько файлов JSON в папке и подпапках, для которых я хочу напечатать только 3 поля. Я использую цикл for для всех файлов json, но для простоты входные данные ниже представляют собой 3 файла json. Для каждого из них я хочу печатать «Имя файла» и «Значение» только тогда, когда внутри каждого файла появляется хотя бы один «Dmo = Path». Если файлы не содержат блока «Dmo = Path», печатайте только имя файла.
{
"Filename": "File_213",
"Date": "2024-4-30",
"Blocks": [
{
"Dmo": "WW",
"Value": "23",
"String": "",
},
{
"Dmo": "Path",
"Value": "/Files/2024/abd",
"String": "",
},
{
"Dmo": "Path",
"Value": "/Files/2024/Ndew",
"String": "",
}
]
}
{
"Filename": "File_4",
"Date": "2024-4-30",
"Blocks": [
{
"Dmo": "WW",
"Value": "45",
"String": "",
}
]
}
{
"Filename": "File_43",
"Date": "2024-4-30",
"Blocks": [
{
"Dmo": "Path",
"Value": "/Files/2023/Roi2",
"String": "",
}
}
]
}
Мой текущий код и текущий вывод, как показано ниже
$ awk '/"Filename":/{fnm=$2}
/Dmo/{dmo=$2}
/Value/ {
val=$2;
if (dmo != "")
print fnm,val
else
print fnm
fnm = ""; dmo = "";val = ""}' input
"File_213", "23",
"/Files/2024/abd",
"/Files/2024/Ndew",
"File_4", "45",
"File_43", "/Files/2023/Roi2",
Мой ожидаемый результат:
File_213, /Files/2024/abd
File_213, /Files/2024/Ndew
File_4
File_43, /Files/2023/Roi2
jq
лучше обрабатывать файлы JSON, чем awk
.
for j in *.json ; do
jq -r '[.Filename, [.Blocks[] | select(.Dmo= = "Path")]] as [$f, $p]
| if $p[0]
then ($p[].Value | [$f, .])
else [$f]
end | @csv' < "$j" ; done
Выход:
"File_213","/Files/2024/abd"
"File_213","/Files/2024/Ndew"
"File_4"
"File_43","/Files/2023/Roi2"
Для структурированных данных, таких как JSON, лучше использовать инструменты, знающие эту структуру, например анализатор JSON jq. Он может обрабатывать несколько входных данных за один вызов, например. с этим прямым подходом:
jq -r '
if any(.Blocks[]; .Dmo == "Path")
then .Filename + (.Blocks[] | ", " + select(.Dmo == "Path").Value)
else .Filename end
' input
.Filename
, .Blocks
, .Dmo
и .Value
получают доступ к соответствующим значениям полей и с прикрепленным []
перебирают значения этого массива.any
выдает true
, если хотя бы одно из предоставленных значений соответствует заданному условию, а select
фильтрует входные значения в соответствии с заданным условием.-r
декодирует результаты строки JSON в необработанный текст (по сути, удаляя двойные кавычки).Это производит
File_213, /Files/2024/abd
File_213, /Files/2024/Ndew
File_4
File_43, /Files/2023/Roi2
В качестве альтернативы можно дедуплицировать часть кода, преобразовав массив .Blocks
в массив с заранее сгенерированными значениями результатов, а затем проверить его на пустоту, используя переменные для справки:
jq -r '
[.Blocks[] | ", " + select(.Dmo == "Path").Value] as $a
| .Filename + if $a == [] then "" else $a[] end
' input
Другой вариант — использовать оператор //
для создания альтернативного значения, которое срабатывает при отсутствии входных данных при итерации по предварительно сгенерированному массиву. (Это основано на очевидном вторичном ограничении, согласно которому все значения .Value
на самом деле являются строками, т. е. не false
или null
.)
jq -r '.Filename + (.Blocks | map(", " + select(.Dmo == "Path").Value)[] // "")'
он отлично работает и краткий сценарий. Поскольку в моем случае речь идет об обработке нескольких файлов в папке и подпапках, как добавить путь к текущему обрабатываемому файлу? это должно быть вне команды jq
? Моя конечная цель - получить представление файлов json в виде «дерева» на основе местоположения файлов и Paths
, отображаемого в каждом файле.
@Rasec Просто замените .Filename
на input_filename
(без предшествующей точки!) в любом из приведенных выше примеров, чтобы получить путь к обрабатываемому в данный момент входному файлу. Если вы хотите удалить имя файла (просто сохраните путь к содержащей его папке), используйте строковые функции, чтобы удалить все, начиная с последнего символа-разделителя пути /
, например. (input_filename | sub("/[^/]+$"; ""))
.
Я перешел на jq -r 'input_filename + (.Blocks | map(", " + select(.Dmo == "Path").Value)[] // "")' file
для обработки одного файла, и на выходе отображается имя файла без полного пути, вот так filename, /Files/2024/abd
. Затем, если я запущу эту команду внутри цикла for, вместо имени файла с путем я получу <stdin>, /Files/2024/abd
@Rasec input_filename
создает имя, как указано (попробуйте jq … dir1/dir2/file
), или "<stdin>"
, если ввод поступает из STDIN (он был перенаправлен <
или передан по конвейеру |
в jq, а не в имя файла arg). Вообще говоря, вы, вероятно, можете переместить цикл оболочки в jq, чтобы вам не приходилось перенаправлять/передавать какие-либо данные (и это также было бы более эффективно из-за меньшего количества вызовов из оболочки). Альтернативно, используйте опцию --arg
jq для импорта одного значения (имени файла), которое вы можете использовать в структуре JSON для сохранения в качестве реальных данных, даже если фактическая ссылка на файл потеряна из-за перенаправления/конвейера.
Спасибо. Это работает с использованием jq '...' file
вместо jq '...' < file
.
@спасибо за помощь, это работает, я пытаюсь адаптировать его к своим реальным данным, но, похоже, работает.