Patch for Unix filename expansion to try out

4 views
Skip to first unread message

Bram Moolenaar

unread,
Jan 4, 2008, 7:17:42 AM1/4/08
to vim...@vim.org, Charles E Campbell Jr, Adri Verhoef

A couple of problems were detected when the Unix shell is used to expand
file names containing characters such as a single quote. This applies
to completion on the command line and glob().

I made a patch that defines a function to echo each file name
separately. Inspired by a patch that Dasn made, but staying on the safe
side.

The patch for ex_docmd.c changes detecting the end of the file name when
doing command line completion. This specifically fixes a file name
containing "&".

Please try it out and let me know if there are any new problems.

*** ../vim-7.1.197/src/os_unix.c Thu Jan 3 18:55:21 2008
--- src/os_unix.c Fri Jan 4 12:59:41 2008
***************
*** 4946,4951 ****
--- 4946,4954 ----
char_u *p;
int dir;
#ifdef __EMX__
+ /*
+ * This is the OS/2 implementation.
+ */
# define EXPL_ALLOC_INC 16
char_u **expl_files;
size_t files_alloced, files_free;
***************
*** 5056,5075 ****
return OK;

#else /* __EMX__ */
!
int j;
char_u *tempname;
char_u *command;
FILE *fd;
char_u *buffer;
! #define STYLE_ECHO 0 /* use "echo" to expand */
! #define STYLE_GLOB 1 /* use "glob" to expand, for csh */
! #define STYLE_PRINT 2 /* use "print -N" to expand, for zsh */
! #define STYLE_BT 3 /* `cmd` expansion, execute the pattern directly */
int shell_style = STYLE_ECHO;
int check_spaces;
static int did_find_nul = FALSE;
int ampersent = FALSE;

*num_file = 0; /* default: no files found */
*file = NULL;
--- 5059,5084 ----
return OK;

#else /* __EMX__ */
! /*
! * This is the non-OS/2 implementation (really Unix).
! */
int j;
char_u *tempname;
char_u *command;
FILE *fd;
char_u *buffer;
! #define STYLE_ECHO 0 /* use "echo", the default */
! #define STYLE_GLOB 1 /* use "glob", for csh */
! #define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */
! #define STYLE_PRINT 3 /* use "print -N", for zsh */
! #define STYLE_BT 4 /* `cmd` expansion, execute the pattern
! * directly */
int shell_style = STYLE_ECHO;
int check_spaces;
static int did_find_nul = FALSE;
int ampersent = FALSE;
+ /* vimglob() function to define for Posix shell */
+ static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo -n \"$1\"; echo; shift; done }; vimglob >";

*num_file = 0; /* default: no files found */
*file = NULL;
***************
*** 5107,5115 ****

/*
* Let the shell expand the patterns and write the result into the temp
! * file. if expanding `cmd` execute it directly.
! * If we use csh, glob will work better than echo.
! * If we use zsh, print -N will work better than glob.
*/
if (num_pat == 1 && *pat[0] == '`'
&& (len = STRLEN(pat[0])) > 2
--- 5116,5132 ----

/*
* Let the shell expand the patterns and write the result into the temp
! * file.
! * STYLE_BT: NL separated
! * If expanding `cmd` execute it directly.
! * STYLE_GLOB: NUL separated
! * If we use *csh, "glob" will work better than "echo".
! * STYLE_PRINT: NL or NUL separated
! * If we use *zsh, "print -N" will work better than "glob".
! * STYLE_VIMGLOB: NL separated
! * If we use *sh*, we define "vimglob()".
! * STYLE_ECHO: space separated.
! * A shell we don't know, stay safe and use "echo".
*/
if (num_pat == 1 && *pat[0] == '`'
&& (len = STRLEN(pat[0])) > 2
***************
*** 5122,5130 ****
else if (STRCMP(p_sh + len - 3, "zsh") == 0)
shell_style = STYLE_PRINT;
}

! /* "unset nonomatch; print -N >" plus two is 29 */
len = STRLEN(tempname) + 29;
for (i = 0; i < num_pat; ++i)
{
/* Count the length of the patterns in the same way as they are put in
--- 5139,5154 ----
else if (STRCMP(p_sh + len - 3, "zsh") == 0)
shell_style = STYLE_PRINT;
}
+ else if (strstr((char *)p_sh, "sh") != NULL)
+ shell_style = STYLE_VIMGLOB;

! /* Compute the length of the command. We need 2 extra bytes: for the
! * optional '&' and for the NUL.
! * Worst case: "unset nonomatch; print -N >" plus two is 29 */
len = STRLEN(tempname) + 29;
+ if (shell_style == STYLE_VIMGLOB)
+ len += STRLEN(sh_vimglob_func);
+
for (i = 0; i < num_pat; ++i)
{
/* Count the length of the patterns in the same way as they are put in
***************
*** 5183,5192 ****
--- 5207,5220 ----
STRCAT(command, "glob >");
else if (shell_style == STYLE_PRINT)
STRCAT(command, "print -N >");
+ else if (shell_style == STYLE_VIMGLOB)
+ STRCAT(command, sh_vimglob_func);
else
STRCAT(command, "echo >");
}
+
STRCAT(command, tempname);
+
if (shell_style != STYLE_BT)
for (i = 0; i < num_pat; ++i)
{
***************
*** 5232,5239 ****
if (flags & EW_SILENT)
show_shell_mess = FALSE;
if (ampersent)
! STRCAT(command, "&"); /* put the '&' back after the
! redirection */

/*
* Using zsh -G: If a pattern has no matches, it is just deleted from
--- 5260,5266 ----
if (flags & EW_SILENT)
show_shell_mess = FALSE;
if (ampersent)
! STRCAT(command, "&"); /* put the '&' after the redirection */

/*
* Using zsh -G: If a pattern has no matches, it is just deleted from
***************
*** 5265,5271 ****
show_shell_mess = TRUE;
vim_free(command);

! if (i) /* mch_call_shell() failed */
{
mch_remove(tempname);
vim_free(tempname);
--- 5292,5298 ----
show_shell_mess = TRUE;
vim_free(command);

! if (i != 0) /* mch_call_shell() failed */
{
mch_remove(tempname);
vim_free(tempname);
***************
*** 5336,5342 ****
}
vim_free(tempname);

! #if defined(__CYGWIN__) || defined(__CYGWIN32__)
/* Translate <CR><NL> into <NL>. Caution, buffer may contain NUL. */
p = buffer;
for (i = 0; i < len; ++i)
--- 5363,5369 ----
}
vim_free(tempname);

! # if defined(__CYGWIN__) || defined(__CYGWIN32__)
/* Translate <CR><NL> into <NL>. Caution, buffer may contain NUL. */
p = buffer;
for (i = 0; i < len; ++i)
***************
*** 5359,5365 ****
}
}
/* file names are separated with NL */
! else if (shell_style == STYLE_BT)
{
buffer[len] = NUL; /* make sure the buffer ends in NUL */
p = buffer;
--- 5386,5392 ----
}
}
/* file names are separated with NL */
! else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB)
{
buffer[len] = NUL; /* make sure the buffer ends in NUL */
p = buffer;
***************
*** 5438,5444 ****
{
(*file)[i] = p;
/* Space or NL separates */
! if (shell_style == STYLE_ECHO || shell_style == STYLE_BT)
{
while (!(shell_style == STYLE_ECHO && *p == ' ')
&& *p != '\n' && *p != NUL)
--- 5465,5472 ----
{
(*file)[i] = p;
/* Space or NL separates */
! if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
! || shell_style == STYLE_VIMGLOB)
{
while (!(shell_style == STYLE_ECHO && *p == ' ')
&& *p != '\n' && *p != NUL)
*** ../vim-7.1.197/src/ex_docmd.c Wed Jan 2 21:07:32 2008
--- src/ex_docmd.c Thu Jan 3 22:04:21 2008
***************
*** 3338,3349 ****
}
in_quote = !in_quote;
}
#ifdef SPACE_IN_FILENAME
! else if (!vim_isfilec_or_wc(c)
! && (!(ea.argt & NOSPC) || usefilter))
! #else
! else if (!vim_isfilec_or_wc(c))
#endif
{
while (*p != NUL)
{
--- 3338,3350 ----
}
in_quote = !in_quote;
}
+ /* An argument can contain just about everything, except
+ * characters that end the command and white space. */
+ else if (c == '|' || c == '\n' || c == '"' || (vim_iswhite(c)
#ifdef SPACE_IN_FILENAME
! && (!(ea.argt & NOSPC) || usefilter)
#endif
+ ))
{
while (*p != NUL)
{


--
CUSTOMER: You're not fooling anyone y'know. Look, isn't there something
you can do?
DEAD PERSON: I feel happy... I feel happy.
[whop]
CUSTOMER: Ah, thanks very much.
MORTICIAN: Not at all. See you on Thursday.
CUSTOMER: Right.
The Quest for the Holy Grail (Monty Python)

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ download, build and distribute -- http://www.A-A-P.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Tony Mechelynck

unread,
Jan 4, 2008, 8:37:04 AM1/4/08
to vim...@googlegroups.com, vim...@vim.org, Charles E Campbell Jr, Adri Verhoef, Bram Moolenaar
Bram Moolenaar wrote:
>
> A couple of problems were detected when the Unix shell is used to expand
> file names containing characters such as a single quote. This applies
> to completion on the command line and glob().
>
> I made a patch that defines a function to echo each file name
> separately. Inspired by a patch that Dasn made, but staying on the safe
> side.
>
> The patch for ex_docmd.c changes detecting the end of the file name when
> doing command line completion. This specifically fixes a file name
> containing "&".
>
> Please try it out and let me know if there are any new problems.

A quick test of command-line completion seems OK: ":e t<Tab>" (with 'wildmenu'
on) found the files created by the external commands

touch "test 'this & that' filename"
touch "this&'that other'"
touch "test filename with ' single quote"

The filenames were the strings above without the double quotes.
Backslash-escaping looked OK: I could edit the files and save them. Then "ls
t*" in bash didn't show spurious filenames, and these files' lengths weren't
zero anymore. If you have other cases in mind, I'm ready to check.


Best regards,
Tony.
--
hundred-and-one symptoms of being an internet addict:
208. Your goals for the future are obtaining an ISDN connection and
a 6 gig hard drive.

Ben Schmidt

unread,
Jan 4, 2008, 9:02:57 AM1/4/08
to vim...@googlegroups.com
> The filenames were the strings above without the double quotes.
> Backslash-escaping looked OK: I could edit the files and save them. Then "ls
> t*" in bash didn't show spurious filenames, and these files' lengths weren't
> zero anymore. If you have other cases in mind, I'm ready to check.

I will try at some later stage when I have time/will to copy and apply the patch,
but I always had problems when a partially completed filename contained a single
quote where it wouldn't complete further. With

wildmode=longest,list,full

and files with names

It's good
It's bad
It's ugly

the completion would always fail. I would type I<Tab> which would yield "It\'s\ "
typing g<Tab> then would fail with a beep.

Also, it was somewhat odd to obtain, for these files

It is odd
It's odd

If I give I<Tab> the result is "It\". Kinda fair enough as whatever character
comes next does need to be escaped, but still, seems a little strange.

At any rate, those are my experiences and it would be interesting to know if
they're fixed. It is really the quotes that caused problems. Completing works fine
for a set of files like

It is good
It is bad
It is ugly

Cheers,

Ben.


Send instant messages to your online friends http://au.messenger.yahoo.com

Tony Mechelynck

unread,
Jan 4, 2008, 9:28:12 AM1/4/08
to vim...@googlegroups.com

Looks OK here with the patch: after (in bash)

touch "It's good"
touch "It's bad"
touch "It's ugly"

the command ":new I<Tab>" followed by other hits of the tab key, gives
successively

:new It\'s\ _
:new It\'s\ bad_
:new It\'s\ good_
:new It\'s\ ugly_
:new I_
:new It\'s\ bad_

etc., where _ represents the cursor and 'wildmode' was set to
longest:full,full -- IOW, I got the expected results.

Best regards,
Tony.
--
They also surf who only stand on waves.

Tony Mechelynck

unread,
Jan 4, 2008, 9:31:46 AM1/4/08
to vim...@googlegroups.com

Oops, I misread:

:new It\'s\ g<Tab>

still fails with a beep.


Best regards,
Tony.
--
Lie, n.:
A very poor substitute for the truth, but the only one
discovered to date.

Tony Mechelynck

unread,
Jan 4, 2008, 11:11:57 AM1/4/08
to vim...@googlegroups.com, vim...@vim.org, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt, Bram Moolenaar
Bram Moolenaar wrote:
>
> A couple of problems were detected when the Unix shell is used to expand
> file names containing characters such as a single quote. This applies
> to completion on the command line and glob().
>
> I made a patch that defines a function to echo each file name
> separately. Inspired by a patch that Dasn made, but staying on the safe
> side.
>
> The patch for ex_docmd.c changes detecting the end of the file name when
> doing command line completion. This specifically fixes a file name
> containing "&".
>
> Please try it out and let me know if there are any new problems.
>

OK, here are a few more results, with *errors*

1. (Ben Schmidt; I confirm) With the patch, after (in bash)

touch "It's good"
touch "It's bad"
touch "It's ugly"

then, in Vim, command-line completion only works if I don't try to do
completion after typing (or entering via a previous completion) a part-name
which includes an apostrophe:

:e I<Tab>

allows to select from the 'wildmenu' BUT

:e It\'s\ g<Tab>

fails with a beep


2. After editing those files, a Session file is created in error by
":mksession", giving "E77: Too many files" when reloading with the -S option.

Examination of the session script shows lines such as

badd +1 It's\ good
badd +1 It's\ bad
badd +0 It's\ ugly

where the quote is NOT backslash-escaped.

Bram Moolenaar

unread,
Jan 4, 2008, 12:17:00 PM1/4/08
to Tony Mechelynck, vim...@googlegroups.com, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt

Tony Mechelynck wrote:

It appears it all works fine when 'shell' is "sh", but not when it is
"bash". What is the difference between "sh" and "bash" considering
single quotes?

--
Vim is like Emacs without all the typing. (John "Johann" Spetz)

Bram Moolenaar

unread,
Jan 4, 2008, 12:27:09 PM1/4/08
to vim...@googlegroups.com, Tony Mechelynck, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt

I wrote:

My fault. It has nothing to do with bash itself, just that "bash" is
four characters and "sh" is two :-). I'm glad this was tested before
sending it out!

Here is a replacement patch that should deal with "bash" as with "sh":


*** ../vim-7.1.202/src/os_unix.c Thu Jan 3 18:55:21 2008
--- src/os_unix.c Fri Jan 4 18:23:04 2008

+ if (shell_style == STYLE_ECHO && strstr((char *)p_sh, "sh") != NULL)


--
Q: Is selling software the same as selling hardware?
A: No, good hardware is sold new, good software has already been used by many.

Tony Mechelynck

unread,
Jan 4, 2008, 12:54:48 PM1/4/08
to Bram Moolenaar, vim...@googlegroups.com, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt
Bram Moolenaar wrote:
[...]

> It appears it all works fine when 'shell' is "sh", but not when it is
> "bash". What is the difference between "sh" and "bash" considering
> single quotes?
>

I'll try to find out, and the manpage for bash (which also describes how bash
reacts when invoked as sh) is extremely long. I'm getting the info below from
the QUOTING section.

On my system, /usr/bin/sh is a softlink for /bin/bash but "man sh" gives the
manpage for ash.

If Vim escapes the apostrophe in filenames in Session.vim (as created by
:mksession) when the shell is sh but not when it is bash, I suspect the error
is in Vim. Those lines are for Vim (not *sh) consumption after all. (This
means that filenames containing apostrophes will be lost from the buffer list
after ":mks!" followed by ":qa" and "vim -S".)

From both "man bash" and "man sh", single quotes preserve the literal value
of the enclosed string. A single-quoted string cannot itself contain a single
quote, even when preceded by a backslash.

Outside quotes, a backslash preserves the value of the following char, except
that an end-of-line backslash means continuation to the next line.

Bash (but apparently not sh) treats $'string' specially: that is expanded
according to the ANSI C standard, with the following escapes treated specially:
\a alert (bell)
\b backspace
\e escape
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\' single quote
\nnn octal byte
\xHH hex byte
\cx Control-x (where x is anything that can take a Ctrl modifier)
The expanded result is given with single quotes.

$"string" (still in bash) is translated according to the current locale; if
that locale is C or POSIX the $ is ignored. The expanded result is double-quoted.

In double quoted strings, only $ (dollar) ` (backtick) " (double quote),
<newline> and \ (backslash) are special. The backslash is used there to give
them back their literal value. In bash but not sh, ! is special only if
history expansion is enabled.

Best regards,
Tony.
--
Waste not, get your budget cut next year.

Tony Mechelynck

unread,
Jan 4, 2008, 1:08:47 PM1/4/08
to Bram Moolenaar, vim...@googlegroups.com, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt

No patch here for ex_docmd.c

I suppose this patch is a replacement for the part of the previous one dealing
with os_unix.c but that the part dealing with ex_docmd.c should be kept?

Best regards,
Tony.
--
"I am not now, and never have been, a girlfriend of Henry Kissinger."
-- Gloria Steinem

Tony Mechelynck

unread,
Jan 4, 2008, 1:27:42 PM1/4/08
to Bram Moolenaar, vim...@googlegroups.com, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt
Bram Moolenaar wrote:
[...]

> Here is a replacement patch that should deal with "bash" as with "sh":
[...]

Just tested with the new patch (and still in bash). Command-line completion is
now OK for the cases mentioned by Ben Schmidt (":e It\'s\ g<Tab>"). Session
file is unchanged BUT Vim accepts it -- files with apostrophes in their names
aren't lost from the buffer list now, even though the apostrophes aren't
backslash-escaped in the session script.

In


:e It's\ g<Tab>

completion gives now

:e It\'s\ good

adding a \ before the apostrophe. The correct file is edited regardless of
whether there is a \ before the ' or not.


Best regards,
Tony.
--
"You mean there really is an answer?"
"Yes! But you're not going to like it!"
"Oh do please tell us!"
"You're really not going to like it!"
"but we MUST know - tell us"
"Alright, the answer is...."
"yes..."
"... is ..."
"yes... come on!"
"is 42!"
(Douglas Adams - The Hitchhiker's Guide to the Galaxy)

Bram Moolenaar

unread,
Jan 4, 2008, 4:35:19 PM1/4/08
to Tony Mechelynck, vim...@googlegroups.com, Charles E Campbell Jr, Adri Verhoef, Ben Schmidt

Tony Mechelynck wrote:

> >> It appears it all works fine when 'shell' is "sh", but not when it is
> >> "bash". What is the difference between "sh" and "bash" considering
> >> single quotes?
> >
> > My fault. It has nothing to do with bash itself, just that "bash" is
> > four characters and "sh" is two :-). I'm glad this was tested before
> > sending it out!
> >
> > Here is a replacement patch that should deal with "bash" as with "sh":
> >
> >
> > *** ../vim-7.1.202/src/os_unix.c Thu Jan 3 18:55:21 2008
> > --- src/os_unix.c Fri Jan 4 18:23:04 2008
>
> No patch here for ex_docmd.c
>
> I suppose this patch is a replacement for the part of the previous one
> dealing with os_unix.c but that the part dealing with ex_docmd.c
> should be kept?

Yes, the patch to ex_docmd.c fixes the problem for command line
completion with & in the file name. I'm going to bring them out later
separately, since they really are two different problems. But one of
the file names use for testing requires both patches.

--
WOMAN: King of the who?
ARTHUR: The Britons.
WOMAN: Who are the Britons?
ARTHUR: Well, we all are. we're all Britons and I am your king.


The Quest for the Holy Grail (Monty Python)

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\

Dasn

unread,
Jan 4, 2008, 9:08:38 PM1/4/08
to vim...@googlegroups.com
On 04/01/08 18:27 +0100, Bram Moolenaar wrote:
>! * STYLE_VIMGLOB: NL separated
>! * If we use *sh*, we define "vimglob()".

I have been worried about the matched name which contains NL.

$ cat test.vim
call writefile([], "{foo")
call writefile([], "{foo\nfoo")

$ vim -c "so test.vim"
~
~
:set wildmenu
:set shell=sh
:e \{<Hit Tab>
:" wildmenu returns ["test.vim" , "{foo", "{foo"], note the duplicate "{foo"s.
:e <Hit Tab>
:" wildmenu returns ["test.vim" , "{foo", "{foo<00>foo"], looks right,
:" but it will cause an error when we choose "{foo<00>foo" to edit.
:set shell=csh
:e \{<Hit Tab>
:" nothing got matched.

IMHO, interacting with different shells is a complicated task. What
about defining a Vim's own interface to the file system? Then if
something should be expanded, such as environment variables, '~', etc,
it can be direct processed in Vim via system interface (like getenv(3)),
other than carefully interacting with various shells.
--
Dasn

Bram Moolenaar

unread,
Jan 5, 2008, 8:06:32 AM1/5/08
to Dasn, vim...@googlegroups.com

Dasn wrote:

> On 04/01/08 18:27 +0100, Bram Moolenaar wrote:
> >! * STYLE_VIMGLOB: NL separated
> >! * If we use *sh*, we define "vimglob()".
>
> I have been worried about the matched name which contains NL.

File names with an embedded NL are not supported. They are uncommon and
generally frowned upon.

> $ cat test.vim
> call writefile([], "{foo")
> call writefile([], "{foo\nfoo")
>
> $ vim -c "so test.vim"
> ~
> ~
> :set wildmenu
> :set shell=sh
> :e \{<Hit Tab>
> :" wildmenu returns ["test.vim" , "{foo", "{foo"], note the duplicate "{foo"s.
> :e <Hit Tab>
> :" wildmenu returns ["test.vim" , "{foo", "{foo<00>foo"], looks right,
> :" but it will cause an error when we choose "{foo<00>foo" to edit.
> :set shell=csh
> :e \{<Hit Tab>
> :" nothing got matched.

An alternative would be to try using NUL characters to separate the
items. Unfortunately I could not find a way to make this work for the
sh of FreeBSD that I'm using. The builtin echo command accepts -n and
-e, but not at the same time. That's weird, but that's how it is. So
you could use -e to add a NUL character, but you also get a NL then.

> IMHO, interacting with different shells is a complicated task. What
> about defining a Vim's own interface to the file system? Then if
> something should be expanded, such as environment variables, '~', etc,
> it can be direct processed in Vim via system interface (like getenv(3)),
> other than carefully interacting with various shells.

Vim already expands most things without using a shell. The shell is
invoked only for expanding things that only the shell knows about.

--
The question is: What do you do with your life?
The wrong answer is: Become the richest guy in the graveyard.
(billionaire and Oracle founder Larry Ellison)

Ben Schmidt

unread,
Jan 5, 2008, 10:23:16 AM1/5/08
to vim...@googlegroups.com
It works for me. Quite nice. I've known about this for years, but it never
occurred to report it or try to fix it! It's lovely that it works now, though; I
have a lot of files like this that I use quite regularly in Vim, so it will
improve my quality of life considerably.

Grins,

Dasn

unread,
Jan 6, 2008, 4:34:10 AM1/6/08
to vim...@googlegroups.com
On 05/01/08 14:06 +0100, Bram Moolenaar wrote:
>
>An alternative would be to try using NUL characters to separate the
>items. Unfortunately I could not find a way to make this work for the
>sh of FreeBSD that I'm using. The builtin echo command accepts -n and
>-e, but not at the same time. That's weird, but that's how it is. So
>you could use -e to add a NUL character, but you also get a NL then.
>

Oh, yes, 'echo' is not portable. The printf(1) should be used for this
case. As stated in the SUSV:

http://www.opengroup.org/onlinepubs/009695399/utilities/echo.html

> APPLICATION USAGE
>
> It is not possible to use echo portably across all POSIX systems
> unless both -n (as the first argument) and escape sequences are
> omitted.
>
> The printf utility can be used portably to emulate any of the
> traditional behaviors of the echo utility ...

# tested in FreeBSD, OpenBSD and a Linux
$ for i in * ; do env printf "%s\0" $i; done

--
Dasn

Bram Moolenaar

unread,
Jan 6, 2008, 9:09:32 AM1/6/08
to Dasn, vim...@googlegroups.com

Dasn wrote:

We haven't been using printf so far, thus this needs to be tested to
find any system where it doesn't work. The opengroup isn't always
right (I don't think they cover Cygwin).

"echo" is a builtin, "printf" is a program. I think this means it will
be a bit slower, right? Not sure if this matters.

Also, since printf is a program, isn't there a risk that with some
weird arguments something strange may go wrong?

Why do you put "env" before "printf"?


--
With sufficient thrust, pigs fly just fine.
-- RFC 1925

Tony Mechelynck

unread,
Jan 6, 2008, 9:57:12 AM1/6/08
to vim...@googlegroups.com
Bram Moolenaar wrote:
[...]

> "echo" is a builtin, "printf" is a program. I think this means it will
> be a bit slower, right? Not sure if this matters.
>
> Also, since printf is a program, isn't there a risk that with some
> weird arguments something strange may go wrong?
>
> Why do you put "env" before "printf"?
>
>

On my system, both echo and printf exist as both a bash builtin and an
external executable. "My" echo (GNU) accepts additional options to -n (only
option mentioned in the opengroup article, and not on all systems): -e
(interpret backslash-escapes) and -E (backslash is a normal display character).

env causes the following command on the same line to be searched in the $PATH,
disregarding any shell builtins, as can be seen from the following:

$ env printf --version
printf (GNU coreutils) 6.9
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software. You may redistribute copies of it under the terms of
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.

$ printf --version
bash: printf: --: invalid option
printf: usage: printf [-v var] format [arguments]


Best regards,
Tony.
--
When love is gone, there's always justice.
And when justice is gone, there's always force.
And when force is gone, there's always Mom.
Hi, Mom!
-- Laurie Anderson

Matt Wozniski

unread,
Jan 6, 2008, 5:14:50 PM1/6/08
to vim...@googlegroups.com
On Jan 6, 2008 9:09 AM, Bram Moolenaar wrote:
> We haven't been using printf so far, thus this needs to be tested to
> find any system where it doesn't work. The opengroup isn't always
> right (I don't think they cover Cygwin).

The cygwin developers regularly reference opengroup man pages on the
cygwin mailing lists; so it's a safe bet that if their printf doesn't
adhere to opengroup's requirements they would consider it a bug and
fix it (and their coreutils maintainer is quite responsive and gets
out bugfixes quickly.) That being said, I don't have access to a
cygwin machine until I'm at work tomorrow, so I'll test then if there
isn't a response that it does or doesn't work sooner.

> "echo" is a builtin, "printf" is a program. I think this means it will
> be a bit slower, right? Not sure if this matters.

IIUC, the biggest difference it makes is whether or not execution will
require a fork. On most systems, the speed difference would be
negligible, but on cygwin systems where fork needs to be emulated (and
is very slow as a result), it might be better to not use the preceding
"env"; using the builtin if available and falling back to the binary
if not. Also, perhaps I missed it, but in a quick reading of the
standards I couldn't find any requirement that 'echo' be builtin...

> Also, since printf is a program, isn't there a risk that with some
> weird arguments something strange may go wrong?

A quick test says no:
printf "%s\n" --version>
correctly prints "--version", with gnu printf, bash printf, and zsh
printf, meaning that even those that recognize arguments (gnu printf
normally recognizes --version) know to ignore them after the format
specification. The only problem I can possibly see is exceding the
maximum length of an argument list, which I don't think will be able
to happen if we only printf one file per printf call; using a for-loop
as suggested above.

> Why do you put "env" before "printf"?

So, there ought to be no problems with


for i in *; do

printf "%s\0" $i
done

for bourne-like shells (tested with bash, ash, dash, and busybox's sh)
but clearly that loop won't work for [t]csh... can we assume that
/bin/sh always points us to a bourne-compatible shell? Or,
alternately, can we check if $SHELL is known not to be
bourne-compatible and exec something different? If so, I see no
problem with this approach, since "printf" seems to be quite a good
deal more rigidly specified than "echo", and it wouldn't hurt our
ability to use our shell's extended globs; a zsh loop of:

for i in **/*(.-); do


printf "%s\0" $i
done

should continue to work just as well, if not better, than:
echo **/*(.-)

~Matt

Dasn

unread,
Jan 6, 2008, 6:52:00 PM1/6/08
to vim...@googlegroups.com
On 06/01/08 17:14 -0500, Matt Wozniski wrote:
>
>So, there ought to be no problems with
>for i in *; do
> printf "%s\0" $i
>done
>

Hmm, I found some tricky stuff this morning, just one

$ printf "%s\0" *

is enough. Tested in freebsd, obsd, linux.
I don't know why and whether it will work on other platforms. :)

--
Dasn

Matt Wozniski

unread,
Jan 6, 2008, 7:12:51 PM1/6/08
to vim...@googlegroups.com

Sure; that's not tricky at all, and it works with the caveat I
mentioned above... If "printf" is an external binary, this needs to
go through an exec call, which means we could create a command line +
environment that's larger than ARG_MAX, causing the call to fail.
That's much, much less likely to happen when passing a single file
name.

~Matt

Dasn

unread,
Jan 6, 2008, 8:02:01 PM1/6/08
to vim...@googlegroups.com

Yes, not tricky :). It was described in SUSV of printf(1):
9. The format operand shall be reused as often as necessary to
satisfy the argument operands.

--
Dasn

Dasn

unread,
Jan 6, 2008, 9:53:41 PM1/6/08
to vim...@googlegroups.com
On 06/01/08 17:34 +0800, Dasn wrote:

>$ for i in * ; do env printf "%s\0" $i; done

This has a performance issue.

On obsd with ksh (without builtin printf):

$ ls -1 | wc -l
10003
$ f() for i in *; do printf "%s\0" $i; done;
$ time f > f.txt
2m1.07s real 0m2.92s user 0m15.17s system

# Note: Too many matches will cause "Argument list too long" error
$ time printf "%s\0" * > printf.txt
0m0.10s real 0m0.07s user 0m0.03s system

# A similar alternative, not accurate.
$ time find . -name '*' -print0 > find.txt
0m0.12s real 0m0.07s user 0m0.04s system
--
Dasn

Charles E Campbell Jr

unread,
Jan 10, 2008, 4:35:16 PM1/10/08
to Bram Moolenaar, vim...@googlegroups.com
Bram Moolenaar wrote:

>A couple of problems were detected when the Unix shell is used to expand
>file names containing characters such as a single quote. This applies
>to completion on the command line and glob().
>
>I made a patch that defines a function to echo each file name
>separately. Inspired by a patch that Dasn made, but staying on the safe
>side.
>
>The patch for ex_docmd.c changes detecting the end of the file name when
>doing command line completion. This specifically fixes a file name
>containing "&".
>
>Please try it out and let me know if there are any new problems.
>
>

Verhoef sent me a directory structure:

a & b/
one
two

a's & b's/
three
four

Netrw works for the first "a & b", and does so with the new patch.
However, it does not work for the second set ("a's & b's"); the glob()
for it returns the empty string:

...netrw debugging info..
||||glob(dirname</home/cec/.vim/VIMSCRIPT/TST/NETRW/Verhoef/a's & b's/*>)=
...more netrw debugging info

I used ksh under Fedora Core Linux and a vim compiled with the patch.

Regards,
Chip Campbell


Bram Moolenaar

unread,
Jan 11, 2008, 3:01:35 PM1/11/08
to Charles E Campbell Jr, vim...@googlegroups.com

Charles Campbell wrote:

I can reproduce the problem. Only with ksh, it works fine with tcsh,
bash and sh.

--
TALL KNIGHT: When you have found the shrubbery, then you must cut down the
mightiest tree in the forest ... with a herring.
"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

Bram Moolenaar

unread,
Jan 12, 2008, 4:15:39 PM1/12/08
to Charles E Campbell Jr, vim...@googlegroups.com

Charles Campbell wrote:

I first said I could reproduce it, but that was because I didn't have
any ksh installed. I now installed one, and it appears to work just
fine for me.

There is more than one version of ksh. I'm using this version:

KSH_VERSION='@(#)PD KSH v5.2.14.2 99/07/13.2'

What I did is:

:set shell=ksh
:echo glob("/tmp/a's & b's/*")
/tmp/a's & b's/c + d
/tmp/a's & b's/four


If it still doesn't work for you, please give the glob() that you
actually used.

--
An indication you must be a manager:
You give constructive feedback to your dog.

Dasn

unread,
Jan 12, 2008, 5:33:36 PM1/12/08
to vim...@googlegroups.com

Hmm, I might be boring bossy, but still expecting to use glob(3) or
fnmatch(3) directly ...

--
Dasn

Bram Moolenaar

unread,
Jan 13, 2008, 8:00:23 AM1/13/08
to Dasn, vim...@googlegroups.com

Dasn wrote:

> Hmm, I might be boring bossy, but still expecting to use glob(3) or
> fnmatch(3) directly ...

They are not available on all systems. And only do normal wildcards,
which is something that Vim does itself anyway. We need the shell for
things like backticks.

--
ARTHUR: But if he was dying, he wouldn't bother to carve
"Aaaaarrrrrrggghhh". He'd just say it.
BROTHER MAYNARD: It's down there carved in stone.
GALAHAD: Perhaps he was dictating.


"Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\

Adri Verhoef

unread,
Jan 14, 2008, 3:49:00 PM1/14/08
to vim...@googlegroups.com
On Fri, Jan 04, 2008 at 12:17:42 +0000, Bram Moolenaar wrote:
>
>
> A couple of problems were detected when the Unix shell is used to expand
> file names containing characters such as a single quote. This applies
> to completion on the command line and glob().
>
> I made a patch that defines a function to echo each file name
> separately. Inspired by a patch that Dasn made, but staying on the safe
> side.
>
> The patch for ex_docmd.c changes detecting the end of the file name when
> doing command line completion. This specifically fixes a file name
> containing "&".
>
> Please try it out and let me know if there are any new problems.

I tried it out and found a problem. This is my directory structure:

a'b/
DS=70
DS 70
DS=D66 + S70

There is a directory "a'b" and it contains three files.
The first filename ("DS=70") doesn't contain any spaces, the other two do.

Now I start Vim. At first, I'm inputting ":n a" followed by a TAB.
The screen will display ":n a\'b/", which is correct. Then I type a TAB again
and again and again.
$ vim
:n a<TAB>
:n a\'b/

:n a\'b/<TAB><TAB><TAB>
:n a\'b/DS=70
:n a\'b/DS=70
:n a\'b/DS=70
(Nothing happens, only one filename matches)

:n a\'b/*6<TAB>
(Nothing happens, no filename matches)
:n a\'b/*6*0<TAB>
(Nothing happens, no filename matches)
:n a\'b/DS\ 7<TAB>
(Nothing happens, no filename matches)

:n a\'b/DS\<TAB>
:n a\'b/DS\*


Adri (Vim 7.1 + patches 1-200 plus Bram's "Patch for Unix filename expansion")

Dasn

unread,
Jan 14, 2008, 5:27:21 PM1/14/08
to vim...@googlegroups.com

I can't reproduce this with 1-229. Patch 223 has been released, have a
try?

--
Dasn

Tony Mechelynck

unread,
Jan 15, 2008, 8:08:01 AM1/15/08
to vim...@googlegroups.com

There were two successive versions of Bram's patch in this thread (the second
version replacing only part of the first), and both have been released since
then. If Adri was using the first version, and it was corrected by the second
one, that would explain it.

Best regards,
Tony.
--
... My pants just went on a wild rampage through a Long Island Bowling
Alley!!

Adri Verhoef

unread,
Jan 15, 2008, 7:33:45 PM1/15/08
to vim...@googlegroups.com
On Tue, Jan 15, 2008 at 13:08:01 +0000, Tony Mechelynck wrote:
>
> Dasn wrote:
> > On 14/01/08 21:49 +0100, Adri Verhoef wrote:
> >>> Please try it out and let me know if there are any new problems.
> >> I tried it out and found a problem. This is my directory structure:
> >>
> >> a'b/
> >> DS=70
> >> DS 70
> >> DS=D66 + S70
> >>
> >> There is a directory "a'b" and it contains three files.
> >> The first filename ("DS=70") doesn't contain any spaces, the other two do.
> >>
> >> Now I start Vim. At first, I'm inputting ":n a" followed by a TAB.
> >> The screen will display ":n a\'b/", which is correct. Then I type a TAB again
> >> and again and again.
> >> $ vim
> >> :n a<TAB>
> >> :n a\'b/
> >>
> >> :n a\'b/<TAB><TAB><TAB>
> >> :n a\'b/DS=70
> >> :n a\'b/DS=70
> >> :n a\'b/DS=70
> >> (Nothing happens, only one filename matches)

[snip]

> >> Adri (Vim 7.1 + patches 1-200 plus Bram's "Patch for Unix filename expansion")
> >>
> >
> > I can't reproduce this with 1-229. Patch 223 has been released, have a
> > try?
> >
>
> There were two successive versions of Bram's patch in this thread (the second
> version replacing only part of the first), and both have been released since
> then. If Adri was using the first version, and it was corrected by the second
> one, that would explain it.

Bram's "Patch for Unix filename expansion" is in fact patches 7.1.223 + 7.1.226.

My shell is Bash on Fedora 7. (se sh=/bin/sh)
-rwxr-xr-x 1 root root 801504 2007-11-26 16:03 /bin/bash
lrwxrwxrwx 1 root root 4 2008-01-14 08:58 /bin/sh -> bash

Anyway, I tried reversing Bram's "Patch for Unix filename expansion", then
apply #223, yet it fails to see the filenames containing a space character.

This time I tried this directory structure:

b'c/file001
b'c/file 01
b'c/file 1

Three files, one with one space in its name, one with two spaces in its name
(between 'e' and '1'). Typing:
:n b\'c/*<TAB><TAB> or :n b\'c/<TAB><TAB> (without the asterisk)
only shows one filename: file001

My next try was to put only one file in a directory called "c'd", like so:

$ mkdir c\'d
$ touch c\'d/one\ space
$ src/vim
:n c\'d/<TAB><TAB>
(This just beeps at me. No filename matching.)

One other try then, without the apostrophe to ensure that we don't need a
backslash at first (for locating the directory).
$ mkdir de
$ touch de/sp\ a\ ce
$ src/vim
:n de/*<TAB><CR>
"de/sp a ce" 0L, 0C
(Result: OK!)

OK, then back to directory "c'd" which contains the filename "one space".
$ src/vim
:n c\'d/*
"c'd/one" 0L, 0C
:arg
[c'd/one] space

So this is where something goes wrong, if anyone can follow me.
With patches 7.1.0-200 + 7.1.223 + 7.1.226.

Adri

Adri Verhoef

unread,
Jan 15, 2008, 7:51:57 PM1/15/08
to vim...@googlegroups.com
Well, I don't know what happened, but after one more batch of tests
suddenly everything seems to work that was earlier reported as bad.
One thing that was strange though: Vim reported to have patch #215
installed twice, while I can't remember I downloaded that one even
once. :-|

Everyone, I'll promise to find another case that doesn't work next time! :-)

Thanks for anticipating,
Adri

Bram Moolenaar

unread,
Jan 16, 2008, 9:31:49 AM1/16/08
to Adri Verhoef, vim...@googlegroups.com

Adri Verhoef wrote:

If I'm not mistaken then this message came after the one about
reproducing the problem. Also, I don't appear to be able to reproduce
the problem with a quote in the directory name and a space in the file
name. When 'shell' is "sh" or "bash".

So I'll remove this from my todo list. Can always put it back when you
discover another weird name that doesn't work.

--
Why don't cannibals eat clowns?
Because they taste funny.

Reply all
Reply to author
Forward
0 new messages