'Use Deno.run to extract a file with 7z

Trying to do a little script to automate some tasks. Chose deno as a nice self-contained way to do random automation tasks I need - and a learning opportunity.

One of the things I'm trying to do is extract an archive using 7z and I can't figure out why it's not working.

let cmdProcess = Deno.run({
    cmd: ["7z", "e ProcessExplorer.zip"],
    stdout: "piped",
    stderr: "piped"
})
const output = await cmdProcess.output()
const outStr = new TextDecoder().decode(output);
console.log(outStr)

const errout = await cmdProcess.stderrOutput()
const errStr = new TextDecoder().decode(errout);
console.log(errStr)

7z does run, according to the normal output. But I receive the following error no matter what parameters I try to pass to 7z:

Command Line Error:
Unsupported command:
x ProcessExplorer.zip

It doesn't matter if I supply the full path or relative, or what command I give.

It's possible that I'm supplying the wrong arguments to Deno.run, but I've been unable to google Deno.run() because most search result end up being for the deno run CLI command.

I am on Win 10 21H2.
deno v1.19.3



Solution 1:[1]

It should work if you split the e subcommand from the argument ProcessExplorer.zip:

const cmdProcess = Deno.run({
  cmd: ["7z", "e", "ProcessExplorer.zip"],
  stdout: "piped",
  stderr: "piped",
});

With Deno.run you need to split all the different subcommands/options/flags of a command into separate strings in the cmd array, as is mentioned in this thread

For documentation on the Deno namespace API you can find it at https://doc.deno.land. For Deno.run specifically you can find it here.

Solution 2:[2]

Here's a basic functional abstraction for extracting archives with 7z using Deno:

./process.ts:

const decoder = new TextDecoder();

export type ProcessOutput = {
  status: Deno.ProcessStatus;
  stderr: string;
  stdout: string;
};

/**
 * Convenience wrapper around subprocess API.
 * Requires permission `--allow-run`.
 */
export async function getProcessOutput(cmd: string[]): Promise<ProcessOutput> {
  const process = Deno.run({ cmd, stderr: "piped", stdout: "piped" });

  const [status, stderr, stdout] = await Promise.all([
    process.status(),
    decoder.decode(await process.stderrOutput()),
    decoder.decode(await process.output()),
  ]);

  process.close();
  return { status, stderr, stdout };
}

./7z.ts:

import { getProcessOutput, type ProcessOutput } from "./process.ts";

export { type ProcessOutput };

// Ref: https://sevenzip.osdn.jp/chm/cmdline/index.htm
export type ExtractOptions = {
  /**
   * Extract nested files and folders to the same output directory.
   * Default: `false`
   */
  flat?: boolean;

  /**
   * Destination directory for extraction of archive contents.
   * 7-Zip replaces the `"*"` character with the name of the archive.
   * Default: (the current working directory)
   */
  outputDir?: string;

  /** Overwrite existing files. Default: `true` */
  overwrite?: boolean;
};

/**
 * Extract the contents of an archive to the filesystem using `7z`.
 * Requires `7z` to be in your `$PATH`.
 * Requires permission `--allow-run=7z`.
 *
 * @param archivePath - Path to the target archive file
 * @param options - Extraction options
 */
export function extractArchive(
  archivePath: string,
  options: ExtractOptions = {},
): Promise<ProcessOutput> {
  const {
    flat = false,
    outputDir,
    overwrite = true,
  } = options;

  const cmd = ["7z"];

  // https://sevenzip.osdn.jp/chm/cmdline/commands/extract.htm
  // https://sevenzip.osdn.jp/chm/cmdline/commands/extract_full.htm
  cmd.push(flat ? "e" : "x");

  // https://sevenzip.osdn.jp/chm/cmdline/switches/overwrite.htm
  cmd.push(overwrite ? "-aoa" : "-aos");

  // https://sevenzip.osdn.jp/chm/cmdline/switches/output_dir.htm
  if (outputDir) cmd.push(`-o${outputDir}`);

  // Disable interaction
  // https://sevenzip.osdn.jp/chm/cmdline/switches/yes.htm
  cmd.push("-y");

  cmd.push(archivePath);

  return getProcessOutput(cmd);
}


Example usage:

./main.ts:

import { extractArchive, type ExtractOptions } from "./7z.ts";

async function main() {
  const { status: { code, success }, stdout, stderr } = await extractArchive(
    "example.zip",
  );

  if (!success) { // Something went wrong
    console.error(`7-Zip exited with code: ${code}`);
    console.error(stderr);
    Deno.exit(1);
  }

  // Extraction was successful
  console.log(stdout);
}

if (import.meta.main) main();

> deno run --allow-run=7z main.ts
Check file:///C:/Users/deno/so-71445897/main.ts

7-Zip 21.07 (x64) : Copyright (c) 1999-2021 Igor Pavlov : 2021-12-26

Scanning the drive for archives:
1 file, 774 bytes (1 KiB)

Extracting archive: example.zip
--
Path = example.zip
Type = zip
Physical Size = 774

Everything is Ok

Folders: 3
Files: 2
Size:       38
Compressed: 774

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 jsejcksn