3

I have a directory containing hundreds of zip files. I want to be able to select about fifty files and extract them all at once using unzip (or 7z) in the terminal. The files have completely different names, so I cannot use a regular expression to match them all.

As a simplified example, I have the following three files:

41a02d81b1c7b6225b11908c38b820cc.zip
d581697699c4321c32b733a9678.zip
ffb7077a319b2d168d5415b0f59a9e2ba3a.zip

and I want to extract only two of them without typing the commands manually.

Is there a way to use a GUI to select multiple files and then export the list to bash to be processed by a command?

1
  • Possible solution (although not on same site) tell me if it worked or not askubuntu.com/a/1280489/1130604 (Obviously Replace the command with the unzip command, ) Even if this does not work, or is perfect (note: It isn't) Hopefully it can be helpful for the future Jun 22 at 16:43
5

With the zsh, shell, and provided you have:

setopt auto_menu autolist correct no_list_ambiguous
setopt list_types auto_pushd no_list_beep

zstyle ':completion:*' completer _expand _complete _ignored _correct _approximate _prefix
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
eval "$(dircolors)"
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' max-errors 1 not-numeric
zstyle ':completion:*' menu select=2
zstyle ':completion:*' original true
zstyle ':completion:*' prompt 'correct> '
zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s'
zstyle ':completion:*' verbose true
zstyle ':completion:*' menu select=2
autoload -Uz compinit
compinit -i

in your ~/.zshrc (note that not all of those are relevant here; and were obtained as a result of running compinstall to tune completion), then you can type:

$ for f (*.zipTab

At that point, that will list the zip files in the current directory and let you select them with arrow keys and Enter.

If instead of pressing Enter to select an entry, you press Alt+A, it will insert the current entry but also let you carry on selecting more files.

On the last, file, press Enter and then you can finish the command such as:

for f (file1.zip file2.zip) unzip -d $f:r $f

Which for each file will unzip it into a directory with the same name but without the .zip extension (file1.zip in file1/, file2.zip in file2/, etc).

2
  • 1
    Interesting. I didn't know zsh had a TUI-like facility.
    – EmmaV
    Jun 19 at 14:14
  • Me neither, @Stéphane Chazelas thanks for this answer* - extremely elegant! Jun 19 at 14:15
5

In zsh/bash/ksh93 (or with GNU printf), you could do:

$ printf 'unzip %q\n' ./*.zip > commands
$ vi commands

And within vi (or your favourite text editor), remove the lines for the files you don't want to unzip. Save the changes. Then run:

zsh -x ./commands

(replace zsh with your favourite shell, preferably the one you ran the printf command from).


Instead of storing unzip 'file.zip' commands in that file, you can also store just the files:

printf '%s\n' ./*.zip > files

and edit that files.

And assuming none of the file names contain newline characters and with GNU xargs:

xargs -d '\n' -tr0a files -n1 unzip

to unzip them.


A similar approach with zsh would be to do something similar, but instead of editing a file, you edit a variable with the vared builtin:

list=(*.zip)

To seed the $list array with the zip files in the current directory.

vared list

to edit that variable, removing the entries you don't want to unzip. Then:

for f ($list) unzip -d $f:r $f

To unzip all the files in $list.

4
  • Is it possible for unzip to read arguments from a file?
    – EmmaV
    Jun 19 at 14:21
  • @Emmav, you'd typically use xargs for that. See edit. Jun 19 at 14:26
  • xargs doesn't work in this case because unzip et al. accept only one argument.
    – EmmaV
    Jun 21 at 18:56
  • @EmmaV. Thanks. Sorry, my bad. I forgot the -n1 Jun 21 at 20:53
4

You can use iselect for this. Give it a list of things (filenames or whatever), and it pops up a curses dialog box in your terminal allowing you to select one or more items.

It returns a list of selected items, and you can do what you want them them.

I've used it several times in scripts over the years for tasks similar to yours.

Alternatively, Midnight Commander is a full-featured text-mode file browser that lets you navigate directories (and "enter" zip files, i.e. navigate them as if they're a directory), has built in file viewers and simple editor, and lets you select file(s) to use with built-in features or pass as args to external programs.

Midnight Commander is well-known and packaged for most, if not all, distros. iselect is a bit more obscure. On Debian, they're packaged as mc and iselect respectively.

2
  • I've tried using mc. I can create a text file of selected files by using Ctrl-x t, then editing the line to add printf '%s\n' ... > outputFile, but I cannot get each file on a new line automatically. Do you know how to do this?
    – EmmaV
    Jun 19 at 16:19
  • 1
    printf '%s\n' is the way I'd do it, but why would you need to? your script can just have a loop like for f in "$@"; do .....something with "$f"..... ; done to iterate over the filenames? BTW, note that \n as a filename separator isn't 100% safe - \n is a valid character in filenames (and isn't all that uncommon, especially if the filenames came from windows or mac, or other people trying to make the names on their desktop icons readable)
    – cas
    Jun 20 at 2:39
2

Since you have a terminal, the natural approach would be to view the directory in Emacs dired mode (C-x d). You can then select files to operate on with m (or select a group by wildcard with % m) and deselect with u.

Once you've made your selection, you can run a command on the selected files with !.

1

This kind of thing is exactly what the X select/paste conventions are for.

find that/dir; while read; do whatever with $REPLY; done

and then tripleclick to select a line from the listing out of your scrollback buffer and middlemouse click to paste it into the terminal without losing your place in the list.

Or you can fire up any editor worth the name, emacs has its builtin dired, vim has its builtin netrw, and mark the files you want, then do whatever with the marked files.

Or you can generate all the commands into a file and edit out the ones you don't want:

find that/dir -iname \*.zip -printf '7z x %p\n' | vim -R -

then pipe the buffer through a shell.

0

tried find?

find ./ -type f -iname "*.7z" -exec UNZIP_COMMAND_HERE {} \;

edit:

find ./ -type f -iname "[a-z].7z" -exec UNZIP_COMMAND_HERE {} \;

10
  • 1
    I cannot use find for this. I would need to match arbitrary file names; not all files or a regex.
    – EmmaV
    Jun 19 at 13:21
  • 2
    I mean file names that do not match the same pattern or regex, so they have to be entered manually.
    – EmmaV
    Jun 19 at 13:30
  • 1
    Added an example to the question.
    – EmmaV
    Jun 19 at 13:38
  • 1
    The two commands you've written would not match two of the files only. find is not suitable for this task.
    – EmmaV
    Jun 19 at 13:43
  • 4
    @EmmaV, you can replace -exec with -ok here for find to prompt you for each file whether you want to unzip it or not. Jun 19 at 14:21
0

With bash, you can use glob-expand-word (C-x *) to perform a glob expansion and then use edit-and-execute-command (C-x C-e) to edit the command in your default editor:

edit-and-execute-command (C-x C-e)

Invoke an editor on the current command line, and execute the result as shell commands. Bash attempts to invoke $VISUAL, $EDITOR, and emacs as the editor, in that order.

You therefore could type unzip * without pressing Enter, then press C-x * followed by C-x C-e, edit the expanded command to your liking, and then execute the command.

0

Most of these answers are insane; if this is a one-off, just struggle through manually entering file names. Otherwise, if it's a consistent need, determine a pattern, write a solution and version control as your requirements evolve.

Here is a directory with some log files

~ $ ls /var/log | wc -l
46

I am only interested in wifi and system logs for some daily aggregate blah blah.

~ $ ls /var/log/system* /var/log/wifi* | wc -l
22

Create a "Daily Aggregate Blah" strategy and commit (not shown).

$ ls /var/log/system* /var/log/wifi* | wc -l
22
~ $ mkdir /tmp/selectstrategies
~ $ pushd /tmp/selectstrategies/
/tmp/selectstrategies ~
/tmp/selectstrategies $ printf "%s\n" "/var/log/system*" "/var/log/wifi*" | tee /dev/stderr >StrategyDailyAggregrate
/var/log/system*
/var/log/wifi*

Write small utility script to perform whatever task using a strategy file and commit to repo (not shown).

$ cat ./select.sh 
#!/bin/sh
set -eu

<$1 xargs -t  sh -c 'ls $@' -

Run

$ ./select.sh StrategyDailyAggregrate 
sh -c 'ls $@' - '/var/log/system*' '/var/log/wifi*'
/var/log/system.log             /var/log/wifi.log.0.bz2
...

This was off the top of my head, but you get the point; if you need a bespoke solution, just write a simple, dirty and quick mvp. Iterate if needed, but no bells, no whistles.

2
  • I can't determine a pattern because the file names are arbitrary and they vary.
    – EmmaV
    Jun 21 at 20:51
  • @EmmaV Dude, don't be so literal; by pattern, I mean some acceptable strategy. Above I grouped arbitrary files by a made up strategy, which is that I need to aggregate those logs and ship somewhere, yada yada. If the filenames are arbitrary, identify some overlap or repeated usage and build something simple that will cut down on manual entry and repition as much as possible. As you can see from the answers, there isnt a silver bullet, so you're not going to be able to avoid building something. Jun 22 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.