1

I am trying to cleanly, without errors/quirks, open multiple files via command line in the vim text editor. I am using bash as my shell. Specifically, I want to open the first 23 files in the current working directory. My initial command was:

$ ls | head -23 | xargs vim

But when I do this, I get the following error message before all the files open:

Vim: Warning: Input is not from a terminal

and no new text is shown in the terminal after vim exits. I have to blindly do a reset in order to get a normal terminal back, apart from opening a new one.

This seems to be discussed here: using xargs vim with gnu screen, and: Why does "locate filename | xargs vim" cause strange terminal behaviour?

Since the warning occurs, xargs seems to be a no-no with vim. (Unless you do some convoluted thing using subshells and input/output redirection which I'm not too interested in as a frequent command. And using an alias/function... meh.)

The solution seemed to be to use bash's command substitution. So I tried:

$ vim $(ls | head -23)

But the files have spaces and parentheses in them, in this format:

surname, firstname(email).txt

So what the shell then does, which also is the result in the xarg case, is provide surname, and firstname(email).txt as two separate command arguments, leaving me in vim with at least twice the number of files I wanted to open, and none of the files I actually wanted to open.

So, I figure I need to escape the file names somehow. I tried to quote the command:

$ vim "$(ls | head -23)"

Then the shell concatenates the entire output from the substitution and provides that as a single command argument, so I'm left with a super-long file name which is also not what I want.

I've also tried to work with the -exec, -print, -print0 and -printf options of find, various things with arrays in bash, and probably some things I can't remember. I'm at a loss right now.

What can I do to use file names that come from a command, as separate command arguments and shell-quoted so they actually work?

Thanks for any and all help!

Community
  • 1
  • 1
Victor Zamanian
  • 3,100
  • 24
  • 31
  • 1
    May I ask _why_ you want to do this? Perhaps there's a better solution for the _real_ question here. – Donovan Dec 18 '13 at 19:28
  • Sure: I am marking assignments. There are 46 hand-ins in total, and I am to mark the first half and the other half is someone else's responsibility. So I use these first 23 files to write my comments in for those 23 students and the other 23 are of less interest to me during the work. However, I'd still like to keep that other half of files. Otherwise I'd just do: `ls | tail -23 | xargs -d '\n' rm; vim *`. :-) – Victor Zamanian Dec 19 '13 at 20:56

2 Answers2

4

Here's an array-based solution:

fileset=(*)
vim "${fileset[@]:0:23}"
chepner
  • 497,756
  • 71
  • 530
  • 681
  • That's pretty compact. Thanks! Is there a way to make that a one-liner somehow? – Victor Zamanian Dec 18 '13 at 19:15
  • Not that I'm aware of. If the files are named using a consecutive number scheme (file1.txt, file2.txt, etc), you could try `vim file{1..23}.txt` instead. – chepner Dec 18 '13 at 20:04
  • Okay, shame. It was a tidy solution nonetheless. Unfortunately they are not named in such a pattern. Otherwise bracket expansion would definitely be the go-to solution. Thanks again! – Victor Zamanian Dec 19 '13 at 20:44
1
xargs -a <(ls | head -23) -d '\n' vim

-a tells xargs to read arguments from the named file instead of stdin. <(...) lets us pass the output of the ls/head pipeline where a filename is expected.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • Wow, this is exactly what I was looking for. A one-liner that uses basically just one utility, `xargs`, besides `ls` and `head` of course. I didn't know about `<(...)` before. Would you mind telling me what that construct is called so I can `man bash` it? – Victor Zamanian Dec 19 '13 at 20:47