I'm writing a function in my ~/.vimrc file, but I got a problem.
I want to concat a variable and a const string, so I do this like below:
let linux_version=system('uname -r')
let host_kernel_dir= "/lib/modules/" . linux_version . "/build"
echo host_kernel_dir
I wanted result is /lib/modules/4.8.0-52-generic/build, but I got this result
"/lib/modules/4.8.0-52-generic
/build "
So it seems a \n was added. So how to get rid of this \n?
This removes newlines from the system output
let linux_version = substitute(system('uname -r'), '\n\+$', '', '')
system() result must be chomped (for those who have known perl), I use the following:
function! lh#os#system(cmd)
return system(a:cmd)[:-2]
endfunction
Another solution using /proc pseudo filesystem:
let linux_version=readfile('/proc/sys/kernel/osrelease')[0]
Related
I sometimes use the string() function in order to generate parsable strings.
For example, :echo string("hello world") shows:
'hello world'
But if I add real carriage returns into the string, the result becomes unparsable, simply because
the carriage returns are not transformed.
For example, :echo string("hello\nworld") shows:
'hello
world'
I would have expected:
"hello\nworld"
Is there a way to get a parsable version of any string, without writing a dedicated function?
EDIT
To be more precise, I need "parsable" strings to be used with the :execute command, in order to create customizable mappings. The basic idea is to be able to use the following code, even when there are some CRs in the a:toinsert argument:
function! InsertMapping(lhs, toinsert)
let l:rhs = printf('<c-r>=%s<cr>', string(a:toinsert))
exe 'inoremap' a:lhs l:rhs
endf
" This call is working:
call InsertMapping('<c-r><c-e>', "hello world")
" This one throws an error:
call InsertMapping('<c-r><c-e>', "hello\nworld")
" E492: Not an editor command: world')<cr>
Indeed, the last call to InsertMapping() will try to execute:
inoremap <c-r><c-e> <c-r>='hello
world'<cr>
Buf of course, I need to execute instead:
inoremap <c-r><c-e> <c-r>="hello\nworld"<cr>
Please note that I need to keep <c-r>= because my real use case is more complex, and needs some function calls; so the mapping can't be simplified like this:
inoremap <c-r><c-e> hello<cr>world
Don't know what do you exactly mean "parsable". If you want to use eval() to get the original string value, it is working. You see the real linebreak was printed out because of the echo() function, it expanded the \n as a linebreak.
Give this a try:
let a="hi\nworld"
echo a==eval(string(a))
echo a==eval(string("hi\nworld"))
both will return 1 (true). So it is "parsable".
If I didn't understand the meaning of your "parsable", please make some example.
I finally wrote a dedicated function to stringify a vim string; it's far from perfect, but it will give a "correct" output for all the tested cases. When really nothing at all is special inside the string, it returns a single-quoted string, which may slightly improve performance in some rare cases.
Here is the code:
let s:specials = {
\ "\b":'\b', "\e":'\e', "\f":'\f', "\n":'\n',
\ "\r":'\r', "\t":'\t', "\\":'\\', "\"":'\"',
\}
function! Stringify(source)
let output = ''
let string_is_special = v:false
for i in range(strlen(a:source))
let char = a:source[i]
let ascii = char2nr(char)
let char_is_special = v:false
for [key, str] in items(s:specials)
if char == key
let output .= str
let char_is_special = v:true
let string_is_special = v:true
break
endif
endfor
if !char_is_special
if ascii < 32
let output .= printf('\x%02x', ascii)
let string_is_special = v:true
else
let output .= char
endif
endif
endfor
return printf(string_is_special ? '"%s"' : "'%s'", output)
endf
Here are some quick tests:
let tests = [
\ "simple string",
\ 'back \ slash',
\ "carriage \n return",
\ "utf8 frénçh àccènts",
\ "\x47\x6e\x75",
\ "ctrl-w special key: \<c-w>",
\ ]
for s in tests
echo Stringify(s)
endfor
Here is the tests output:
'simple string'
"back \\ slash"
"carriage \n return"
'utf8 frénçh àccènts'
'Gnu'
"ctrl-w special key: \x17"
In Vim, single quotes and double quotes have different behaviors.
Between double quotes, special characters like \n are interpreted, so:
"hello\nworld"
becomes:
hello
world
and you would have to double the \ to actually get a \n:
"hello\\nworld"
Between single quotes, special characters are not interpreted, so:
'hello\nworld'
becomes:
hello\nworld
All of that is explained under :help expr-" and :help expr-'.
Say I have this vimscript as "/tmp/example.vim":
let g:input = "START; % END"
exec("! clear && echo " . shellescape(g:input))
If I open that file and run it with :so %, the output will be
START; /tmp/example.vim END
because the "%" is expanded to the buffer name. I want the output to be
START; % END
I can use the generic escape() method to escape percent signs in particular. This works:
let g:input = "START; % END"
exec("! clear && echo " . escape(shellescape(g:input), "%"))
But is that really the best way? I'm sure there're more characters I should escape. Is there a specific escape function for this purpose? Or a better way to shell out?
For use with the :! command, you need to pass the optional {special} argument to shellescape():
When the {special} argument is present and it's a non-zero
Number or a non-empty String (|non-zero-arg|), then special
items such as !, %, # and <cword> will be preceded by
a backslash. This backslash will be removed again by the |:!|
command.
:exec("! clear && echo " . shellescape(g:input, 1))
You need to properly escape the '%'. So it should be:
let g:input = "START; \\% END"
This seems to do it:
let g:input = "START; % END"
echo system("echo " . shellescape(g:input))
It should be noted I don't really care about the output; I'll use this with silent in a larger script.
The following function does not echo the result variable.
fu! Test()
let input = input(">")
let result = "error\n"
if 1
echo result
endif
endf
Removing the newline from result, removing the input, or removing the if statement will fix this issue. Any ideas why this happens?
In my actual function the result variable is set from executing a system command and I would prefer not parsing/correcting the result before echoing it.
Vimscript can be strange. When I have issues with echo not showing when it should, usually a call to 'redraw' either before or after the echo fixes it for me.
Try replacing the \n newline with \r, as mentioned at “How to replace a character for a newline in Vim?”:
fu! Test()
let input = input(">")
let result = "error\r"
if 1
echo result
endif
endf
Note that in running the above function I do not get the input cleared before result is echoed, so that if I enter >foo for the input, result is echoed directly and I get >fooerror. Echoing a newline before result is echoed takes care of this:
fu! Test()
let input = input(">")
let result = "error\r"
if 1
echo "\r"
echo result
endif
endf
I have a variable set like so:
let filename="/tmp/" . system('date +"%Y%m%d"') . ".txt"
How do I open a new buffer using that variable as filename, like tabnew /tmp/20130117.txt
No system call needed, although vim's docs do mention that strftime isn't available on all systems (I assume only some esoteric ones):
if exists('*strftime')
let fn = strftime('/tmp/%Y%m%d')
exe "tabnew" fn
endif
Taking a cue from Vim: How do I chdir to path in a variable, I did:
let $FILENAME="/tmp/" . system('date +"%Y%m%d"')
tabnew $FILENAME
I just need to figure out how to remove the "#" character at the end of the string output of the system function.
How to get a list of files that match some pattern if filenames may contain \n character?
Update: I want solution in pure vimscript, so that it will depend on nothing but vim.
Update2:
Expected output of glob function
Consider the following script:
:!touch /test ; mkdir /test$'\n' ; touch /test$'\n'/test
:echo glob('/**/test')
/test
/test
/test
That is the output of glob function. I want it be the following:
:echo NewGlob('/**/test')
['/test', '/test
/test']
you may try using ls with -b option. check the man page
:echo split( glob("pattern", '.'), "\r")
If you want the pattern to match files containing \n exclusively, use "*\n*".
EDIT:
I see, the character you use in the filename is the same as the one used by glob() to distinguish results. As a consequence, we can't rely of glob().
ghostdog74 gave a good answer then:
:echo split( system('ls -1bd test*'), "\n")
Of course, this is not portable. But I do not really call this the general case -- I never see this kind of names. If glob() cannot handle this general case, then glob() must be fixed.
May be you can try with embedded python or ruby as arnold suggested. But that isn't portable either.
Try this python program. It will match files like abc\n1, abc\n2abc etc.
#!/usr/bin/env python
import os, re
dirlist = os.listdir('.')
pattern = 'abc\n\\d'
for fname in dirlist:
if re.search(pattern, fname):
print fname.replace('\n', '\\n')
It will replace line end ('\n') characters with "\n" string for clarity.
I finally had to write the following function that returns just the same results as python's os.listdir:
function s:globdir(directory, ...)
return split(glob(escape(a:directory.g:os#pathSeparator, '`*[]\').
\ get(a:000, 0, '*')),
\"\n", 1)
endfunction
function s:GetDirContents(directory)
let dirlist = s:globdir(a:directory)+s:globdir(a:directory, '.*')
let nlnum=len(split(a:directory, "\n", 1))-1
let r=[]
let i=0
let addfragment=""
for directory in dirlist
if i<nlnum
let i+=1
let addfragment=directory."\n"
continue
else
let directory=addfragment.directory
let i=0
let addfragment=""
endif
let tail=fnamemodify(directory, ':t')
if tail==#'.' || tail==#'..'
continue
endif
if directory[0]!=#'/'
let r[-1].="\n".directory
else
call add(r, tail)
endif
endfor
return r
endfunction