![Bash 脚本解决方案](https://linux22.com/image/1152080/Bash%20%E8%84%9A%E6%9C%AC%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.png)
我有一个文件夹,里面有多个子文件夹和子子文件夹。我想将存在于多个子文件夹或子子文件夹中的文件的内容result.txt
连同子文件夹的名称一起打印到 csv 文件中。
这意味着如果文件result.txt
位于
abc/def/result.txt
efg/result.txt
然后我需要一个 csv 文件,其中应该有
1. abc content of its result.txt
2. efg content of its result.txt
等等。
我从以下find
命令开始
find . -iname 'result.txt' "a portion of path" "content">final.csv
我该如何继续?
注意:(2017 年 12 月 8 日)虽然以下解决方案可以在终端上正确显示内容,但当我添加 >final.csv 时,它们都不起作用。如前所述,我的 result.txt 有多行。特定 result.txt 的内容会溢出到不同的单元格中,而不是在单个单元格中。有什么建议吗?
答案1
我认为find
是正确的选择:
find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(cat $0)"' {} \;
示例运行
$ echo r1 >a/b/result.txt
$ echo r2 >c/result.txt
$ tree
.
├── a
│ └── b
│ └── result.txt
└── c
└── result.txt
$ find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(cat $0)"' {} \;
a,r1
c,r2
解释
此find
命令搜索当前目录中或名称下的每个文件result.txt
,并在子 shell 中exec
执行该命令。该命令打印子目录的名称、逗号和文件内容,后跟一行。如果您想将此输出写入文件,只需将 eg 附加到命令中即可。printf
bash
printf
\n
>final.csv
更简单
-printf
建议的方法是钢铁司机:
$ find */ -name 'result.txt' -printf '%H,' -exec cat {} \;
a/,r1
c/,r2
这将在第一列打印一个额外的斜线,您可以通过例如管道输出轻松地将其删除sed 's|/,|,|'
。
将多行result.txt
内容合并到一个单元格中
要用空格替换换行符,只需在上述命令之一中替换为,cat
例如sed ":a;N;\$!ba;s/\n/ /g"
$ find */ -name "result.txt" -exec bash -c 'printf "%s,%s\n" "${0%%/*}" "$(sed ":a;N;\$!ba;s/\n/ /g" $0)"' {} \;
a,r1 r1
c,r2
如果您想要使用其他字符串作为分隔符,请将该/ /
部分替换为/your_delimiter/
,但保留斜线。
答案2
好吧,这里有一种方法(现在已编辑,可以将换行符转换为空格,这要感谢Stack Overflow 上的这个答案):
shopt -s globstar
n=0; for i in **/result.txt; do sed -e ":l;N;\$!bl;s/\n/ /g; s/.*/$((++n))\. "${i%%/*}"\t&/" "$i"; done
您可以添加重定向以写入文件
n=0; for i in **/result.txt; do sed ":l;N;\$!bl;s/\n/ /g; s/.*/$((++n))\. "${i%%/*}"\t&/" "$i"; done > outfile
笔记
n=0
设置变量以增加shopt -s globstar
打开递归通配符来**
查找此目录下的所有文件(shopt -u globstar
之后使用取消设置,或退出 shell 并启动一个新目录):l
为该操作设置标签N
读入两行到模式空间(这允许我们使用\n
)\$!
如果这是文件的最后一行,则不行...我们必须退出,$
因为整个命令是双引号以便 shell 可以展开$i
等。但这$
需要完整地传递给sed
,它表示“文件的最后一行”。我建议使用单引号对于sed
脚本,除非您必须在其中传递 shell 变量。bl
...分支到标签(再做一次)s/old/new
old
用。。。来代替new
s/\n/ /g
对于模式空间中的所有换行符(除最后一个之外),将换行符替换为空格.*
任意数量的任意字符(文件中的任何内容)$((++n))
n
每次循环迭代都会增加\.
文字点(逗号不会被特殊处理sed
;它们将被逐字打印)"${i%%/*}"
我们正在处理的文件路径中当前子目录的第一个子目录的名称(删除第一个 之后的所有字符/
)&
搜索部分中匹配的模式(文件中的任何内容)--
不要将-
后续参数中的前导解释为前置选项标志。这可以防止以 开头的文件名-
被解释为选项。在这种特定情况下,这是不必要的,因为我们明确搜索result.txt
并且只有具有此确切名称的文件才会传递给循环。但是,我已将其包括在内,以防有人需要使用 glob 重用此脚本。
这是一个更易读的版本,它也更易于移植(应该适用于所有版本sed
),因为它使用换行符而不是;
来分隔命令:
#!/bin/bash
shopt -s globstar
n=0
for i in **/result.txt; do
sed ":l
N
\$!bl
s/\n/ /g
s/.*/$((++n))\.,"${i%%/*}",&/" -- "$i"
done > outfile
答案3
Bash 脚本解决方案
#!/bin/bash
# If $1 is not given, find will assume cwd
print_file(){
local inputfile="$1"
while IFS= read -r line || [ -n "$line" ];do
printf "%s\\" "$line"
done < "$inputfile"
}
get_file_info(){
local filepath="$1"
counter=$((counter+1))
parent=${filepath%/*}
if [ "$parent" = "$filepath" ]; then
parent="."
fi
printf "%d,%s," "$counter" "$parent"
}
main(){
if [ -z "$1" ];then
set "."
fi
find "$1" -type f -name "result.txt" -print0 |
while IFS= read -r -d '' path
do
get_file_info "$path"
print_file "$path"
printf "\n"
done
}
main "$@"
其工作方式是,您应该将其保存为文件,例如results2csv.sh
,chmod +x
通过提供脚本的完整路径或将其放入~/bin
文件夹中,运行source ~/.bashrc
并通过名称调用脚本来使之可执行并运行。
此脚本的工作原理如下:
$ ./result2csv.sh things
1,things/thing2,to be or not to be\that's Boolean logic\
2,things/thing1,one potato\two potato\
为脚本提供最顶层目录,它将遍历子目录查找文件并根据您指定的最顶层目录输出文件路径。因此,例如,如果您指定./things
为最顶层,则会导致第一行具有./thing/things2
文件路径。换行符被替换为反斜杠以显示文件内容。请注意,如果未指定目录,它还将假定当前工作目录为“。”。
$ cd things
$ ../result2csv.sh
1,./thing2,to be or not to be\that's Boolean logic\
2,./thing1,one potato\two potato\
你现在要做的就是调用results2csv.sh directory > output.csv
将数据输出到文件中,就完成了
答案4
我不知道如何仅使用终端命令来执行此操作,但我已经使用此线程中的 python 脚本完成了类似的事情:
https://stackoverflow.com/questions/37644441/python-run-script-in-all-subdirectories
通过这个,您可以轻松添加将行写入 CSV 文件的功能:
https://docs.python.org/2/library/csv.html对于 Python 2
https://docs.python.org/3/library/csv.html对于 Python 3