使用 bash 一个一个地读取 zip 文件中的文件
read the files one by one in a zip file using bash
我想打开 .zip 文件中的文件并阅读它们。在这个 zip 文件中,我有许多 .gz 文件,如 a.dat.gz、b.dat.gz 等。
到目前为止我的代码:
for i in $(unzip -p sample.zip)
do
for line in $(zcat "$i")
do
# do some stuff here
done
done
您需要两个循环是正确的。首先,您需要存档中的文件列表。然后,您需要在每个文件中进行迭代。
unzip -l sample.zip |sed '
/^ *[0-9][0-9]* *2[0-9-]* *[0-9][0-9]:[0-9][0-9] */!d; s///
' |while IFS= read file
unzip -p sample.zip "$file" |gunzip -c |while IFS= read line
# do stuff to "$line" here
done
done
这假定 zip 存档中的每个文件本身都是一个 gzip 存档。否则你会从 gunzip 中得到一个错误。
代码漫游
unzip -l archive.zip
将列出内容。它的原始输出如下所示:
Archive: test.zip
Length Date Time Name
--------- ---------- ----- ----
9 2017-08-24 13:45 1.txt
9 2017-08-24 13:45 2.txt
--------- -------
18 2 files
因此我们需要解析它。我选择使用 sed 进行解析,因为它快速、简单,并且可以正确保留空格(如果您的文件名称中包含制表符怎么办?)请注意,如果文件中有换行符,这将不起作用。不要那样做。
sed 命令使用正则表达式 (explanation here) 匹配包含文件名的所有行,文件名本身除外。当匹配器触发时,sed 被告知不要删除 (!d
),这实际上是告诉 sed 跳过任何不匹配的内容(如标题行)。第二个命令 s///
告诉 sed 用空字符串替换先前匹配的文本,因此每行输出一个文件名。这将作为 $file
通过管道传输到 while 循环中。 (read
之前的 IFS=
部分防止从两端删除空格,请参阅下面的注释。)
然后我们可以只解压缩我们正在迭代的文件,再次使用 unzip -p
将其打印到标准输出,以便它可以作为 $line
存储在内部 while 循环中。
实验简化
我不确定这有多可靠,但你可以更简单地做到这一点:
unzip -p sample.zip |gunzip -c |while read line
# do stuff to "$line"
done
这应该有效,因为unzip -p archive
吐出存档中每个文件的内容,所有内容连接在一起,没有任何分隔符或元数据(如文件名) 和 因为 gzip 格式接受将存档连接在一起(参见 my notes on concatenated archives),所以 gunzip -c
管道命令看到原始 gzip 数据并在控制台上将其解压缩出来,这是然后传递给 shell 的 while 循环。在这种方法中,您将缺少文件边界和名称,但速度要快得多。
这比您想象的要在 shell 中稳健地执行起来要难。 (现有答案适用于常见情况,但包含令人惊讶的文件名的档案会混淆它)。更好的选择是使用具有本地 zip 文件支持的语言——例如 Python。 (这还具有不需要多次打开输入文件的优点!)
如果单个文件足够小,您可以在内存中容纳每个文件的几个副本,则以下方法会很好地工作:
read_files() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
for content_file in zf.infolist():
content = zlib.decompress(zf.read(content_file), zlib.MAX_WBITS|32)
for line in content.split("\n")[:-1]:
sys.stdout.write("%s[=10=]%s[=10=]" % (content_file.filename, line))
' "$@"
}
while IFS= read -r -d '' filename && IFS= read -r -d '' line; do
printf 'From file %q, read line: %s\n' "$filename" "$line"
done < <(read_files yourfile.zip)
如果您真的想将文件列出和文件读取操作彼此分开,那么稳健地执行此操作可能如下所示:
### Function: Extract a zip's content list in NUL-delimited form
list_files() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
for content_file in zf.infolist():
sys.stdout.write("%s[=11=]" % (content_file.filename,))
' "$@"
}
### Function: Extract a single file's contents from a zip file
read_file() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
sys.stdout.write(zf.read(sys.argv[2]))
' "$@"
}
### Main loop
process_zip_contents() {
local zipfile=
while IFS= read -r -d '' filename; do
printf 'Started file: %q\n' "$filename"
while IFS= read -r line; do
printf ' Read line: %s\n' "$line"
done < <(read_file "$zipfile" "$filename" | gunzip -c)
done < <(list_files "$zipfile")
}
对上述内容进行抽烟测试——如果按如下方式创建输入文件:
printf '%s\n' '1: line one' '1: line two' '1: line three' | gzip > one.gz
printf '%s\n' '2: line one' '2: line two' '2: line three' | gzip > two.gz
cp one.gz 'name
with
newline.gz'
zip test.zip one.gz two.gz $'name\nwith\nnewline.gz'
process_zip_contents test.zip
...然后我们有以下输出:
Started file: $'name\nwith\nnewline.gz'
Read line: 1:line one
Read line: 1:line two
Read line: 1:line three
Started file: one.gz
Read line: 1: line one
Read line: 1: line two
Read line: 1: line three
Started file: two.gz
Read line: 2: line one
Read line: 2: line two
Read line: 2: line three
我想打开 .zip 文件中的文件并阅读它们。在这个 zip 文件中,我有许多 .gz 文件,如 a.dat.gz、b.dat.gz 等。
到目前为止我的代码:
for i in $(unzip -p sample.zip)
do
for line in $(zcat "$i")
do
# do some stuff here
done
done
您需要两个循环是正确的。首先,您需要存档中的文件列表。然后,您需要在每个文件中进行迭代。
unzip -l sample.zip |sed '
/^ *[0-9][0-9]* *2[0-9-]* *[0-9][0-9]:[0-9][0-9] */!d; s///
' |while IFS= read file
unzip -p sample.zip "$file" |gunzip -c |while IFS= read line
# do stuff to "$line" here
done
done
这假定 zip 存档中的每个文件本身都是一个 gzip 存档。否则你会从 gunzip 中得到一个错误。
代码漫游
unzip -l archive.zip
将列出内容。它的原始输出如下所示:
Archive: test.zip
Length Date Time Name
--------- ---------- ----- ----
9 2017-08-24 13:45 1.txt
9 2017-08-24 13:45 2.txt
--------- -------
18 2 files
因此我们需要解析它。我选择使用 sed 进行解析,因为它快速、简单,并且可以正确保留空格(如果您的文件名称中包含制表符怎么办?)请注意,如果文件中有换行符,这将不起作用。不要那样做。
sed 命令使用正则表达式 (explanation here) 匹配包含文件名的所有行,文件名本身除外。当匹配器触发时,sed 被告知不要删除 (!d
),这实际上是告诉 sed 跳过任何不匹配的内容(如标题行)。第二个命令 s///
告诉 sed 用空字符串替换先前匹配的文本,因此每行输出一个文件名。这将作为 $file
通过管道传输到 while 循环中。 (read
之前的 IFS=
部分防止从两端删除空格,请参阅下面的注释。)
然后我们可以只解压缩我们正在迭代的文件,再次使用 unzip -p
将其打印到标准输出,以便它可以作为 $line
存储在内部 while 循环中。
实验简化
我不确定这有多可靠,但你可以更简单地做到这一点:
unzip -p sample.zip |gunzip -c |while read line
# do stuff to "$line"
done
这应该有效,因为unzip -p archive
吐出存档中每个文件的内容,所有内容连接在一起,没有任何分隔符或元数据(如文件名) 和 因为 gzip 格式接受将存档连接在一起(参见 my notes on concatenated archives),所以 gunzip -c
管道命令看到原始 gzip 数据并在控制台上将其解压缩出来,这是然后传递给 shell 的 while 循环。在这种方法中,您将缺少文件边界和名称,但速度要快得多。
这比您想象的要在 shell 中稳健地执行起来要难。 (现有答案适用于常见情况,但包含令人惊讶的文件名的档案会混淆它)。更好的选择是使用具有本地 zip 文件支持的语言——例如 Python。 (这还具有不需要多次打开输入文件的优点!)
如果单个文件足够小,您可以在内存中容纳每个文件的几个副本,则以下方法会很好地工作:
read_files() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
for content_file in zf.infolist():
content = zlib.decompress(zf.read(content_file), zlib.MAX_WBITS|32)
for line in content.split("\n")[:-1]:
sys.stdout.write("%s[=10=]%s[=10=]" % (content_file.filename, line))
' "$@"
}
while IFS= read -r -d '' filename && IFS= read -r -d '' line; do
printf 'From file %q, read line: %s\n' "$filename" "$line"
done < <(read_files yourfile.zip)
如果您真的想将文件列出和文件读取操作彼此分开,那么稳健地执行此操作可能如下所示:
### Function: Extract a zip's content list in NUL-delimited form
list_files() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
for content_file in zf.infolist():
sys.stdout.write("%s[=11=]" % (content_file.filename,))
' "$@"
}
### Function: Extract a single file's contents from a zip file
read_file() {
python -c '
import sys, zipfile, zlib
zf = zipfile.ZipFile(sys.argv[1], "r")
sys.stdout.write(zf.read(sys.argv[2]))
' "$@"
}
### Main loop
process_zip_contents() {
local zipfile=
while IFS= read -r -d '' filename; do
printf 'Started file: %q\n' "$filename"
while IFS= read -r line; do
printf ' Read line: %s\n' "$line"
done < <(read_file "$zipfile" "$filename" | gunzip -c)
done < <(list_files "$zipfile")
}
对上述内容进行抽烟测试——如果按如下方式创建输入文件:
printf '%s\n' '1: line one' '1: line two' '1: line three' | gzip > one.gz
printf '%s\n' '2: line one' '2: line two' '2: line three' | gzip > two.gz
cp one.gz 'name
with
newline.gz'
zip test.zip one.gz two.gz $'name\nwith\nnewline.gz'
process_zip_contents test.zip
...然后我们有以下输出:
Started file: $'name\nwith\nnewline.gz'
Read line: 1:line one
Read line: 1:line two
Read line: 1:line three
Started file: one.gz
Read line: 1: line one
Read line: 1: line two
Read line: 1: line three
Started file: two.gz
Read line: 2: line one
Read line: 2: line two
Read line: 2: line three