Issue
Assuming I'm in the folder like this:
➜ tmp.lDrLPUOF ls
1.txt 2.txt 3.txt 1.zip 2.rb
I want to put all the filenames of text files into a specific JSON format like this:
{
"": [
{
"title": "",
"file": "1"
},
{
"title": "",
"file": "2"
},
{
"title": "",
"file": "3"
}
]
}
Now I just know how to list all the filenames:
➜ tmp.lDrLPUOF ls *'.txt'
1.txt 2.txt 3.txt
Can I use bash or Perl to achieve this purpose? Thank you very much!
Edit
Thanks for @Charles Duffy and @Shawn 's great answers. But it's my fault to forget another important piece of information——time. I want to put the filenames into the resulting JSON per their creating time.
The creating time is as below:
➜ tmp.lDrLPUOF ls -lTr
total 0
-rw-r--r-- 1 administrator staff 0 Oct 12 09:35:05 2022 3.txt
-rw-r--r-- 1 administrator staff 0 Oct 12 09:35:08 2022 2.txt
-rw-r--r-- 1 administrator staff 0 Oct 12 09:35:12 2022 1.txt
So the resulting JSON I wanted should be like this:
{
"": [
{
"title": "",
"file": "3"
},
{
"title": "",
"file": "2"
},
{
"title": "",
"file": "1"
}
]
}
Solution
{ shopt -s nullglob; set -- *.txt; printf '%s\0' "$@"; } | jq -Rn '
{"": [ input
| split("\u0000")[]
| select(. != "")
| {"title": "",
"file": . | rtrimstr(".txt")
}
]
}
'
Let's break this down into pieces.
On the bash side:
shopt -s nullglob
tells the shell that if*.txt
has no arguments, it should emit nothing at all, instead of emitting the string*.txt
as a result.set --
overwrites the argument list in the current context (because this is a block on the left-hand side of the pipeline that context is transient and won't change"$@"
in code outside the pipe).printf '%s\0' "$@"
prints our arguments, with a NUL character after each one; if there are no arguments at all, it prints only a NUL.
On the jq side:
-R
specifies that the input is raw data, not json.-n
specifies that we don't automatically consume any inputs, but will instead useinput
orinputs
to specify where input should be read.split("\u0000")
splits the input on NULs. (This is important because the NUL is the only character that can't exist in a filename, which is why we usedprintf '%s\0'
on the shell end; that way we work correctly with filenames with newlines, literal quotes, whitespace, and all the other weirdness that's able to exist).select(. != "")
ignores empty strings.rtrimstr(".txt")
removes.txt
from the name.
Addendum: Sorting by mtime
The jq parts don't need to be modified here: to sort by mtime you can adjust only the shell. On a system with GNU find
, sort
and sed
, this might look like:
find . -maxdepth 1 -type f -name '*.txt' -printf '%T@ %P\0' |
sort -zn |
sed -z -re 's/^[[:digit:].]+ //g' |
jq -Rn '
...followed by the same jq given above.
Answered By - Charles Duffy Answer Checked By - Marilyn (WPSolving Volunteer)