使用bash在zip文件中逐个读取文件

时间:2017-08-24 13:34:48

标签: bash shell unzip

我想打开.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

2 个答案:

答案 0 :(得分:3)

你需要两个循环是正确的。首先,您需要存档中的文件列表。然后,您需要在每个文件中进行迭代。

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循环。 (IFS=之前的read部分会阻止从任一端剥离空格,请参阅下面的注释。)

然后我们可以解压缩我们正在迭代的文件,再次使用unzip -p将其打印到标准输出,以便它可以作为$line存储在内部while循环中。< / p>

实验简化

我不确定这会有多可靠,但你可以更简单地做到这一点:

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&#39;循环时。在这种方法中,您将缺少文件边界和名称,但速度要快得多。

答案 1 :(得分:0)

这比你认为在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\0%s\0" % (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\0" % (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=$1
  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