Mac unzip of a Windows zip: fixing garbled file names
A colleague on Windows sends a zip. On the Mac it expands into folders named like 蜀咏悄01.txt, or it expands halfway and stops, or Finder throws an error that says nothing useful. The usual advice is to install a third party unarchiver and move on. That advice skips the part worth knowing, which is that the Mac already ships two tools that behave completely differently on the same archive, and picking the right one costs nothing.
Everything below was run on macOS 26.6.2 against a zip built on purpose with Shift_JIS file name bytes and no Unicode flag, which is what a Japanese Windows machine produces.
The failure is a refusal, not garbled text
Listing the archive with the Terminal unzip command shows the expected mess:
$ unzip -l windows_sjis.zip
Length Date Time Name
--------- ---------- ----- ----
5 00-00-1980 00:00 ・シ・ス・ス_2026・スN・スx.txt
Extraction does something worse than display garbage:
$ unzip -d out windows_sjis.zip
error: cannot create out/・シ・ス・ス_2026・スN・スx.txt
Illegal byte sequence
checkdir error: cannot create out/・ス・ス・ス・ス・ス・ス・スc・ス^
Illegal byte sequence
unable to process ・ス・ス・ス・ス・ス・ス・スc・ス^/・ス¥・ス[・スX.txt.
The command does not write a badly named file. It writes nothing at all for that entry. The raw name bytes are handed to the filesystem, APFS requires valid UTF-8 for a name, Shift_JIS bytes are not valid UTF-8, and the create call fails with EILSEQ.
That distinction matters when an archive mixes ASCII names with Japanese ones. The ASCII entries land, the Japanese ones do not, and the destination folder looks plausibly full. Anyone who does not read the error text walks away believing the extraction finished. Checking the file count against the listing is the only cheap way to catch it.
Why the bytes arrive undecodable
A zip entry has a general purpose bit flag. Bit 11, worth 0x800, declares that the name is stored as UTF-8. When that bit is clear, the format says the name is in the historic code page, and in practice it holds whatever bytes the writing tool used. A Japanese Windows tool wrote Shift_JIS bytes and left the bit clear. Nothing inside the archive says so.
macOS cannot guess. Info-ZIP builds on other systems accept an -O option to name the code page, and that option is missing here:
$ unzip -O CP932 -l windows_sjis.zip
Usage: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]
$ echo $?
10
The Apple build is UnZip 6.00 of 20 April 2009, by Info-ZIP, with modifications by Apple Inc., and it exits 10 on that option. The -U and -UU switches that do exist only change how stored UTF-8 paths are handled. Neither adds a code page. So there is no flag to reach for.
There is a second reason the format cannot rescue itself here. With bit 11 clear, the specification hands the decision to whatever reads the archive, and every reader resolves it using its own locale. The archive that displays perfectly on the sender's machine displays as garbage on a machine set to a different language, and neither side is doing anything wrong. Nothing inside the file records which interpretation was intended, so no amount of inspecting the archive recovers that intent. The encoding has to be supplied from outside, by the person who knows where the file came from.
Reading an archive before extracting it
Extracting first and inspecting the wreckage afterwards is the slow order. A listing costs nothing and answers most of the questions in advance.
$ unzip -l suspect.zip | tail -3
The last lines give the total entry count. Keeping that number is what makes a partial extraction visible later, because the count of files that actually landed can be compared against it rather than eyeballed. On an archive with hundreds of entries this is the difference between noticing a problem now and noticing it after the deadline.
The listing also shows two other things worth catching early. Entries beginning with __MACOSX/ mean the archive came from a Mac and carries metadata that the receiving side does not need. Entries whose names run past a couple of hundred characters are worth measuring, because the filename limit on this filesystem is 255 characters rather than 255 bytes. Measured on macOS 26.6.2, a name of 255 Japanese characters was created without complaint and 256 failed with File name too long. Japanese names rarely reach that, but a deep folder tree from a Windows share plus a long document title occasionally does, and the failure again arrives as a single skipped entry inside otherwise successful output.
The command that already handles it
ditto is installed at /usr/bin/ditto on every Mac. Given the exact archive that unzip refused:
$ ditto -x -k windows_sjis.zip out2
$ ls out2
打ち合わせ議事録
請求書_2026年度.txt
Correct names, exit status 0, no third party install. This is the single highest value change available to anyone who unzips Windows archives regularly, and it is one command.
| Tool | Ships with macOS | Result on a Shift_JIS zip |
|---|---|---|
unzip |
Yes | Entries with Japanese names fail with Illegal byte sequence |
ditto -x -k |
Yes | Names decoded correctly |
python3 from the Command Line Tools |
With Xcode or the Command Line Tools | Works, but the code page has to be stated |
| Third party unarchivers | No | Varies by tool, most let the code page be chosen |
When the automatic decode guesses wrong
Archives from Chinese or Korean Windows machines carry different code pages, and a guess can land on the wrong one. Then the code page has to be stated outright. The python3 at /usr/bin/python3 is version 3.9.6 on macOS 26.6.2 and needs no install beyond the Command Line Tools:
import zipfile, os, unicodedata
z = zipfile.ZipFile("windows_sjis.zip")
for info in z.infolist():
name = info.filename.encode("cp437").decode("cp932")
name = unicodedata.normalize("NFC", name)
dest = os.path.join("out", name)
os.makedirs(os.path.dirname(dest), exist_ok=True)
with open(dest, "wb") as f:
f.write(z.read(info))
The encode("cp437") step looks strange and is the important part. Python already decoded the raw bytes as CP437 because the archive claimed nothing better, so encoding back to CP437 recovers the original bytes, and cp932 then reads them properly. Swap cp932 for gbk or cp949 for Chinese or Korean archives. The normalize("NFC") line is covered further down and is worth keeping.
Sending a zip back to Windows
The same flag causes the reverse problem, and this direction is measurable in advance. The zip command bundled with macOS is Zip 3.0 (July 5th 2008), by Info-ZIP, with modifications by Apple Inc. Compressing a folder named 資料 and reading the flags back:
$ zip -qr made_by_zip.zip 資料
$ python3 -c "import zipfile; [print(hex(i.flag_bits), i.filename) for i in zipfile.ZipFile('made_by_zip.zip').infolist()]"
0x0 資料/
0x0 資料/仕様書.txt
The names are UTF-8 bytes, but bit 11 is clear, so a Windows machine reads them as its own ANSI code page and shows garbage. The Info-ZIP switch that sets the flag is rejected here:
$ zip -qr -UN=UTF8 out.zip 資料
zip error: Invalid command arguments (short option 'N' not supported)
ditto -c -k produces the same clear flag and adds __MACOSX entries on top. The Python module in the same install does set the bit:
$ python3 -m zipfile -c out.zip 資料
0x800 資料/
0x800 資料/仕様書.txt
For a one off transfer, plain ASCII names are still the most reliable answer. For anything recurring, python3 -m zipfile -c is a short command that produces an archive Windows reads correctly. A file manager with a built in terminal keeps that command next to the folder it applies to, which is most of why the Features page treats the shell and the file list as one surface rather than two apps.
The ._ residue
An archive built with ditto -c -k --sequesterRsrc carries entries like __MACOSX/資料/._仕様書.txt. Those hold Apple metadata and are noise on the receiving end. /usr/sbin/dot_clean merges or removes ._ files in a directory, and zip -x '*.DS_Store' '__MACOSX/*' keeps them out of a new archive in the first place.
Two spellings of the same name
One trap survives a clean extraction. Japanese characters with dakuten have two valid Unicode spellings. Composed, ぱぴぷ is 7 bytes. Decomposed, the same visible text is 10 bytes, because each mark is stored separately.
APFS is normalization insensitive and form preserving. Measured both directions on macOS 26.6.2: a file created with the composed name is reachable through the decomposed path and vice versa, and os.listdir returns whichever form was used at creation time. So the Mac shows one file and one name, while a zip, a Linux server, or a Windows share sees two distinct names.
This is the quiet source of names that look identical yet fail to match, of a synced folder that keeps two copies, and of a Git repository that reports a rename nobody made. Git carries core.precomposeUnicode for exactly this, documented in the git config reference. Normalizing to NFC at the point of extraction, as the script above does, keeps the problem from entering the tree.
What Apple documents, and what it leaves out
The official page on compressing and expanding files gives one line of troubleshooting:
If you can't open the .zip file, make sure you have enough space on your Mac for the unzipped item. If you received the .zip file from someone else, there might be a problem with the file. Ask them to zip the file again and resend it. Source: support.apple.com
Disk space, or ask the sender to redo it. Neither applies to a Shift_JIS archive, and the sender cannot fix it from a Windows machine without changing tools. That gap is why the same question keeps getting asked, and why the answer has to come from the command line rather than from Finder.
Agreeing something with the sender
Every fix above is local, applied after the archive arrives. Where the same two people exchange files every week, the cheaper move is upstream, and it is worth knowing which requests actually help.
Asking the sender to compress again with the same tool changes nothing, because the tool will write the same bytes and leave the same flag clear. Asking them to switch archive formats moves the problem rather than removing it, since any container that stores names without declaring their encoding inherits the same ambiguity. The flag is a zip mechanism, and the equivalent guarantee in another format has to be checked rather than assumed.
Two requests do work. The first is to keep file names inside the archive to ASCII, with the descriptive Japanese title living in the document itself or in the accompanying message. That sounds like a step backwards and it removes the failure completely, which is why long running exchanges tend to drift toward it on their own. The second is to send a folder rather than an archive when the transport allows it, because a shared drive or a transfer service carries names as text through a path that declares its encoding, rather than through a container that does not.
Where neither is possible, the receiving side absorbs the problem, and absorbing it well means having the decode step written down rather than rediscovered. A short script that takes an archive and a code page and produces a correctly named folder is perhaps fifteen lines, and it stops the whole question from being reopened every few months. Keeping that script beside the folder it operates on, rather than in a snippets file in another app, is a large part of why the Compared with other file managers page treats the terminal as part of the file view rather than a separate window.
What to change first
Stop double clicking Windows archives and run ditto -x -k archive.zip destination instead. When the names still come out wrong, state the code page with the Python snippet above rather than trying more flags on unzip. Keeping that command within reach of the folder it belongs to is the point of a file manager that carries a terminal, which is what Atriens is built around.
Frequently asked questions
Why does Finder sometimes open the archive fine when the Terminal command fails?
They are different code paths. The Terminal unzip hands raw name bytes to the filesystem and fails when those bytes are not valid UTF-8. The archive expansion built into macOS, which ditto -x -k also reaches, decodes the names first. Measured on the same archive, unzip refused every Japanese entry and ditto extracted all of them correctly.
Do the garbled names mean the file contents are damaged?
No. Only the name is affected. The compressed data is untouched, which is why decoding the name with the right code page and writing the bytes out recovers a perfectly good file. If the contents themselves were corrupt, the extraction would report a CRC error instead of a name error.
How can the flag be checked before sending an archive to someone on Windows?
Read the general purpose bit flag of each entry. A one line command using the bundled Python prints it, and 0x800 means the names are declared as UTF-8. Anything else means a Windows machine will fall back to its own code page. Checking takes a second and saves a round trip.
Is a third party unarchiver still worth installing?
It depends on how often the code page has to be chosen by hand. For occasional Windows archives, ditto -x -k covers the common case with nothing installed. For archives arriving from several locales every week, a tool that exposes a code page menu removes the guesswork. Pricing and platform details differ per tool, so compare them against the built in commands before paying for one.