I want to write a bash script that starts up a Vim session using a specific configuration.

For instance consider a C project with the following structure

 - src
   - file1.c
   - file2.c
   - Makefile
 - include
   - file1.h
   - file2.h
   - Makefile
 - tests
   - test_file1.c
   - test_file2.c
   - Makefile
 - Makefile

I can already write something like

vim project_dir/src/*.c project_dir/include/*.h project_dir/tests/*.c project_dir/Makefile

to open all the files as buffers.

However apart from this single file per window tab, I would like to open couple more tabs like one tab which displays related src and header files as vertical splits and another tab that opens certain test source code and relevant src files as vertical splits.

I do not however even know how to accomplish the simple task of say calling Vim from terminal so that there would be two tabs on startup. I tried using vim -c as per some of the threads I saw here but never managed to get any command executed.

I guess project specific vimrc files could be one way (I think can figure out how to do this from the vimrc) but would prefer to try to do it as terminal commands in a bash script if possible.

For using -c I tried the following:



c1=''"'"':tabnew '"${src_files[0]}"''"'"''
vim_args="${src_files[@]} ${header_files[@]} -c"
vim_args="${vim_args} ${c1}"

vim "${vim_args}"

echo "vim ${vim_args}"

I died a little when trying to include the single quotes correctly maybe there is a better way. However the strange thing is although when I call this script the second tab does not open, if I just copy paste the echoed command and run it directly from the shell it works fine. Not sure what I am missing here? The echoed command is

vim /home/avicenna/local_packages/test/src/file1.c /home/avicenna/local_packages/test/src/file2.c /home/avicenna/local_packages/test/include/file1.h /home/avicenna/local_packages/test/include/file2.h -c ':tabnew /home/avicenna/local_packages/test/src/file1.c'

Note that if I replace c1 above by

c1="\":tabnew "${src_files[0]}"\""

I still have the same problem. Running the bash script does not open the tab but copy pasting and executing the command in terminal does.

Update: Changing

vim "${vim_args}"


eval "vim ${vim_args}"

did the trick somehow, I have no idea why.

  • 2
    "I tried using vim -c as per some of the threads I saw here but never managed to get any command executed." Let's see what you tried.
    – romainl
    Commented May 4, 2023 at 7:14
  • 1
    Welcome to vi SE. You may want to take a look at :help :mksession. You should be able to put everything in a session file. It would be a middle way between bash script and a project-specific vimrc.
    – Friedrich
    Commented May 4, 2023 at 8:58
  • 1
    @romainl I now figured out I was not using it correctly. vim1 file1.txt -c ":tabnew" seems to work. But it seems the session idea suggested by Friedrich seems like a more canonical way to do what I want. Thanks
    – Sina
    Commented May 4, 2023 at 9:15
  • for people interested: I also found this which is using -c argument indeed to do it stackoverflow.com/questions/73876317/…
    – Sina
    Commented May 4, 2023 at 9:19
  • Once you figure out how to solve your problem, please post an answer so others can benefit from it. Thank you.
    – Friedrich
    Commented May 4, 2023 at 9:25

2 Answers 2



A session is a file of vimscript commands, which you can create manually for full control or with :mksession to preserve an existing layout (with caveats). I'm particularly fond of https://github.com/tpope/vim-obsession with sessions.

Start a session with vim -S <session>; the default is Session.vim and can be launched with vim -S.


You tried this, but the problem is that "${vim_args}" doesn't word-split the expansion of the vim_args variable. You could unquote it, but then it's also subject to globbing and files with spaces would not be supported. It seemed like you were a bit confused on how shell words and quoting worked. Further, echo is a poor debug tool because of how it works (even print '%s\n' isn't enough here, but might have helped you spot the problem). You can always use declare -p in Bash to check the value of a variable.

With a modern Bash that supports arrays, I would write like this. Don't use a literal string of arguments if you don't need to; it's unnecessarily complicated and brittle. Always ShellCheck.

#! /bin/bash


# unused: perhaps you meant to include it?

  -c ":tabnew ${src_files[0]}"

# use exec to throw away the intermediate shell process; don't do this if you
have commands after `vim` in the script
exec vim "${args[@]}"

In terms of achieving it from bash (see Friedrich's comment above for alternate method) I was able to come up with the following code which works



c1="\":tabnew "${src_files[0]}" | :vs "${header_files[0]}"\""
c2="\":tabnew "${test_files[0]}" | :vs "${header_files[0]}"\""

vim_args="${src_files[*]} ${header_files[*]} -c ${c1} -c ${c2}"

eval "vim ${vim_args}"

Funny enough eval was necessary only calling vim ${vim_args} does not work!

  • 1
    You're never declaring test_files.
    – Friedrich
    Commented May 5, 2023 at 10:04
  • added them now thanks. Also it would seem based on ShellCheck that when you want to include a list in a string * is a better practice than @ so changed that too
    – Sina
    Commented May 5, 2023 at 15:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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