30

When I open some bash script files with vim it sometimes identifies them as conf files, that's okay, I can just correct that by setting the filetype to sh with :setf sh.

That great, except I've noticed that this doesn't fix things entirely:

Two files side-by-side with different highlighting

Notice that shopt is properly highlighted on the left, but not on the right, where I manually set the filetype to sh.

This means that when a file is identified as bash or sh by vim, it sets the filetype to sh but then does some extra steps that I'm not doing when I set the filetype manually.

Any one know what that might be, and how I could fix it?

Conner
  • 30,144
  • 8
  • 52
  • 73
Peter Coulton
  • 54,789
  • 12
  • 54
  • 72

4 Answers4

33

vim already recognizes many file types by default. Most of them work by file extensions, but in a case like this, vim will also analyze the content of the file to guess the correct type.

vim sets the filetype for specific file names like .bashrc, .tcshrc, etc. automatically. But a file with a .sh extension will be recognized as either csh, ksh or bash script. To determine what kind of script this is exactly, vim reads the first line of the file to look at the #! line.

If the first line contains the word bash, the file is identified as a bash script. Usually you see #!/bin/bash if the script is meant to be executed directly, for some other shell configuration file you should use the file extensions .bash.

The help in vim explains this as well at :help ft-bash-syntax. You can also use let g:is_bash=1 in your .vimrc to make bash syntax highlighting the default for all files with filetype=sh. If you want to look at the details, this is implemented in $VIMRUNTIME/filetype.vim.

raimue
  • 4,322
  • 1
  • 21
  • 31
  • 1
    I'm marking this as the answer because it nicely explains the why, as well as providing a perfectly reasonable solution. I've also added an answer detailing my own solution. – Peter Coulton Sep 17 '11 at 23:07
  • 1
    **_Ugh_.** While the introductory synopsis is helpful, the presented solution (e.g., `let g:is_bash=1` in `.vimrc`) is unhelpful. Why? Because doing so coerces non-Bash shell script buffers (e.g., Bourne, Korn) to use Bash-specific syntax highlighting. Consider [Tim Friske](https://stackoverflow.com/users/881945/tim-friske)'s [excellent solution](https://stackoverflow.com/a/13445254/2809027) instead. – Cecil Curry Aug 17 '17 at 04:56
  • @CecilCurry That is exactly why I added the fair warning that this affects every file with `filetype=sh`. Actually, I just reproduced what `:help ft-bash-syntax` recommends to add to your `.vimrc` to make this the global default. It will not force this syntax for all shell files. A more specific shebang like `#!/bin/ksh` will still override this *default* choice, so I cannot reproduce your concern. – raimue Aug 20 '17 at 15:38
8

It turns out that syntax/sh.vim includes specific highlighting for Korn, Bash and sh, you just have to tell it which you're using. This is done with b:is_kornshell, b:is_bash and b:is_sh respectively.

Depending on the situation I figure I'll use the following:

ftdetect/bash.vim:

au BufRead,BufNewFile *bash* let g:is_bash=1
au BufRead,BufNewFile *bash* setf sh

Modeline:

# vim:let g:is_bash=1:set filetype=sh:

Key Mapping

nmap <silent> <leader>b :let g:is_bash=1<cr> :setf sh<cr> 
Peter Coulton
  • 54,789
  • 12
  • 54
  • 72
  • 1
    +1 Sweet! The modeline approach would be my favorite. (If I cared anough about such minor deviations :)) – sehe Sep 16 '11 at 22:34
  • @Peter Coulton: Didn't you mean to set the `b:is_bash` variable local to the buffer instead of the `g:is_bash` variable global. At least your code snippet doesn't reflect this circumstance. Also you could concatenate the two `au` commands by ending the first with `let b:is_bash = 1 | setfiletype sh`. – Tim Friske Nov 18 '12 at 22:18
  • 1
    I don't see the benefit of setting the global every time you load a file with 'bash' in its filename vs. just setting it once in your `.vimrc` and being done with it. – dash-tom-bang May 15 '15 at 22:26
  • 2
    Unfortunately for this use case, it is not possible to set a variable in a modeline. – John Freeman Apr 14 '16 at 13:55
  • **Non-ideal.** Globally enabling `g:is_bash=1` enables Bash-specific syntax highlighting for *all* non-Bash shell script buffers (e.g., Bourne, Korn) – which is bad. Locally enabling `b:is_bash=1` is vastly preferable. Likewise, per-filetype `ftdetect/` files have largely been obsoleted by a single `filetype.vim` file unifying *all* filetype detection. For a solution that addresses both, see [Tim Friske](https://stackoverflow.com/users/881945/tim-friske)'s [excellent solution](https://stackoverflow.com/a/13445254/2809027). – Cecil Curry Aug 17 '17 at 04:51
  • @Peter-Coulton I'm using vim v8.1 but your modeline returns an error : `Error detected while processing modelines: line 1: E518: Unknown option: let` – SebMa Aug 01 '22 at 18:54
4

Similar to Peter Coulton's solution and documented as well as an alternative in the section "new-filetype" of the "filetype" Vim help the ~/.vim/filetype.vim file could contain the following code:

if exists("did_load_filetypes")
  finish
endif

augroup filetypedetect
  au! BufRead,BufNewFile *bash* let b:is_bash = 1 | setfiletype sh
augroup END

This approach has the following implications:

  1. There is one ~/.vim/filetype.vim file instead of one for each file type under the ~/.vim/ftdetect directory.
  2. The b:is_bash variable is set local to the buffer as opposed to global by referring to it as g:is_bash.
Tim Friske
  • 2,012
  • 1
  • 18
  • 28
3

Try viewing the effective syntax setting

:windo echo b:current_syntax

(I kind of expect the first window to say bash, and the second to say sh...?)

Also try mucking with the synatx synchronisation:

:windo syn sync fromstart
:windo syn sync minlines=300

In general

:he syn-sync

for more information


PS.

A long shot, but some other highlighting might be interfering:

:windo se @/=''
:match none
:2match none
:3match none
sehe
  • 374,641
  • 47
  • 450
  • 633