My first part of the requirement:

I would like to extract a single file from ex1234.zip. The structure and contents of ex1234.zip:

ex1234 (directory)
    ex1234 (directory)

I want to be able to extract only ex1234.csv file but will not know the name.

Second part is to able to do this for all exXXXX.zip that sit in the same directory.


Output will be:


Real sample:

$ less CW2178470.zip
Archive:  CW2178470.zip
Zip file size: 26108 bytes, number of entries: 26
-rw----     2.0 fat      108 bl defN 15-Aug-04 09:37 CW2178470/CW2178470.csv
-rw----     2.0 fat     1363 bl defN 15-Aug-04 09:37 CW2178470/config/BusinessContactApprovers.csv
-rw----     2.0 fat      158 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/announcements.xml
-rw----     2.0 fat     1037 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/Plan/plan.xml
-rw----     2.0 fat      141 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/Plan/tasks.xml
-rw----     2.0 fat     2408 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/FI_Doc208411460_doc.xml
-rw----     2.0 fat      215 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/MessageBoard/nb_27482kst.26ihyzj_.htm
-rw----     2.0 fat     2364 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/MessageBoard/messageboard.xml
-rw----     2.0 fat     1250 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/team.xml
-rw----     2.0 fat    22016 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/Doc208411460.doc
-rw----     2.0 fat     9973 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/audithistory.xml
-rw----     2.0 fat     6731 bl defN 15-Aug-04 09:37 CW2178470/CW2178470/ws.xml
-rw----     2.0 fat      308 bl defN 15-Aug-04 09:37 CW2178470/xsd/WSFolder.xsd
-rw----     2.0 fat     4897 bl defN 15-Aug-04 09:37 CW2178470/xsd/Task.xsd
-rw----     2.0 fat      770 bl defN 15-Aug-04 09:37 CW2178470/xsd/ContractWorkspace.xsd
-rw----     2.0 fat     4754 bl defN 15-Aug-04 09:37 CW2178470/xsd/AuditHistory.xsd
-rw----     2.0 fat    25564 bl defN 15-Aug-04 09:37 CW2178470/xsd/CommonTypes.xsd
-rw----     2.0 fat     5657 bl defN 15-Aug-04 09:37 CW2178470/xsd/MessageBoard.xsd
-rw----     2.0 fat     2471 bl defN 15-Aug-04 09:37 CW2178470/xsd/Plan.xsd
-rw----     2.0 fat      337 bl defN 15-Aug-04 09:37 CW2178470/xsd/InternalContractWorkspace.xsd
-rw----     2.0 fat     1045 bl defN 15-Aug-04 09:37 CW2178470/xsd/SalesContractRequest.xsd
-rw----     2.0 fat     3133 bl defN 15-Aug-04 09:37 CW2178470/xsd/FolderItem.xsd
-rw----     2.0 fat      906 bl defN 15-Aug-04 09:37 CW2178470/xsd/ContractRequest.xsd
-rw----     2.0 fat     8973 bl defN 15-Aug-04 09:37 CW2178470/xsd/WorkspaceTypes.xsd
-rw----     2.0 fat     4645 bl defN 15-Aug-04 09:37 CW2178470/xsd/Team.xsd
-rw----     2.0 fat      781 bl defN 15-Aug-04 09:37 CW2178470/xsd/SalesContractWorkspace.xsd
26 files, 112005 bytes uncompressed, 21940 bytes compressed:  80.4%
  • Can you do a less ex1234.zip and paste the exact output ?
    – neuron
    Aug 6, 2015 at 16:32
  • Was a bit lengthy but I added a real sample to the post that wouldn't fit here. Aug 6, 2015 at 16:52
  • Does the csv file name always match the zip file name?
    – Sobrique
    Aug 7, 2015 at 11:44
  • You'd have to write a script(let) that lists the contents of the zipfile, single out the exact file you want, and then extract just that one. Do it by hand, unless this is a routine task. If it is, use your favorite scripting language to cobble something together.
    – vonbrand
    Aug 7, 2015 at 11:46

5 Answers 5


You could use unzip like this:

unzip -j file[.zip] [file] [-x xfile]

where -j means junk paths, file[.zip] is your archive name, [file] is the archive member to be processed and [-x xfile] is the list of archive members to be excluded from processing. All these options are described in detail in the man page.
So in your case, running for example:

unzip -j ex1234.zip '*/*.csv' -x '*/*/*'

will extract in the current directory all files matching *.csv from depth level 2 in the ex1234.zip archive (excluding archive members from depth level 3 and below as '*/*/*' means paths that match at least two /).

Now, to process all the archives in the current directory you could run:

for zipfile in *.zip; do unzip -j "$zipfile" '*/*.csv' -x '*/*/*'; done

which extracts the .csv file from each archive in the current directory (that's why -j is needed).
In your particular case, there's no .csv on level 1 depth so you could also run:

for zipfile in *.zip; do unzip -j "$zipfile" '*.csv' -x '*/*/*'; done

which should yield the same result.
To dry-run and see which files will be extracted (their archive paths) without actually extracting them, replace -j with -qql:

for zipfile in *.zip; do unzip -qql "$zipfile" '*/*.csv' -x '*/*/*'; done

As a side note, the -j option could be omitted iff the .csv files to be extracted were on depth level 1 (i.e. no parent dir); in that case you could simply run:

for zipfile in *.zip; do unzip "$zipfile" '*.csv' -x '*/*'; done

Tried with unzip provided by Debian:

UnZip 6.00 of 20 April 2009, by Debian. Original by Info-ZIP.

for file in ex*.zip
  unzip -j $file '*.csv'
  • This is a very good answer but is it possible to only limit to ex1234 directory (very first directory) because I do not want other CSV files that may exist in other folders. Aug 6, 2015 at 18:56

Use a FUSE-based filesystem to access the zip file as a directory tree. Mount each zip file, then access it with normal method (shell wildcards, cp command, etc.).

With fuse-zip:

mkdir mnt
for z in *.zip; do
  fuse-zip -- "$z" mnt
  set mnt/*.csv
  if [ $# -gt 1 ]; then
    echo "Skipping $z because it contains multiple .csv files"
  elif ! [ -e "$1" ]; then
    echo "Skipping $z because it does not contain a .csv file"
    cp -- "$1" "${z%.zip}.csv"
  fusermount -u mnt

You can do the same with archivemount instead of fuse-zip.

There's also AVFS which works differently: it creates a view of the whole filesystem under ~/.avfs; in this view, if you have an archive file /path/to/foo.zip, you can access it as a directory under the name ~/.avfs/path/to/foo.zip#.

cd "$HOME/.avfs$PWD"
for z in *.zip; do
  set -- "$z#/"*.csv
  if [ $# -gt 1 ]; then
    echo "Skipping $z because it contains multiple .csv files"
  elif ! [ -e "$1" ]; then
    echo "Skipping $z because it does not contain a .csv file"
    cp "$1" "${z%.zip}.csv"

If you use a shell with arrays and with a way to get an empty list when a wildcard doesn't match, you can get a slightly more readable script. For example, in ksh93, with fuse-zip:

mkdir mnt
for z in *.zip; do
  fuse-zip -- "$z" mnt
  if ((${#csv[@]} > 1)); then
    echo "Skipping $z because it contains multiple .csv files"
  elif ((${#csv[@]} == 0)); then
    echo "Skipping $z because it does not contain a .csv file"
    cp -- "$1" "${z%.zip}.csv"
  fusermount -u mnt

In zsh, use csv=($z/*.csv(N)). In bash, use csv=($z/*.csv) but first run shopt -s nullglob.

  • Very thorough answer unfortunately our environment doesn't have either tools (fuse, AVFS) nor do I have authority over it. Thank you. Aug 7, 2015 at 15:54

You suggest that all the names have to match:

  • The ZIP filename
  • The directory name inside the ZIP file
  • The CSV file inside the directory

In that case, try:

for zipfile in ./*.zip; do
    base="$(basename "$zipfile" .zip)"
    unzip "$zipfile" "$base/$base.csv"

On the assumption that all the files match this pattern - from CW2178470.zip you need to always extract CW2178470/CW2178470.csv

This is relatively easy:

for i in ./*.zip
   SERIAL=$(echo "$i" | sed -e 's,^.*/,,' -e 's,.zip$,,' )
   unzip "$i" "${SERIAL}/${SERIAL}.csv"

If you need smarter logic than that, I'd probably start looking at perl and Archive::Zip to extract.

  • Thanks! This works too, but folder is constructed with csv file inside. i.e ex1234/ex1234.csv ... Aug 7, 2015 at 15:45
  • unzip -j is your friend there.
    – Sobrique
    Aug 7, 2015 at 16:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .