zsh-users
 help / color / mirror / code / Atom feed
* Re: for loop with parameter expansion in zsh vs. bash
       [not found] <CADn-QaM6ZEP32i+6t_DC4bA8iJF_VR5hVsP6bMyxqMATxazVCw__45247.5188104019$1446533685$gmane$org@mail.gmail.com>
@ 2015-11-03  7:35 ` Stephane Chazelas
  2015-11-03  7:47   ` Alexander Skwar
       [not found]   ` <CADn-QaNAt3y2qd4dyBnhd9ifdMOqf9DsVwFbVfdriap6uohvRA__32237.289628438$1446536964$gmane$org@mail.gmail.com>
  0 siblings, 2 replies; 6+ messages in thread
From: Stephane Chazelas @ 2015-11-03  7:35 UTC (permalink / raw)
  To: Alexander Skwar; +Cc: zsh-users

2015-11-03 07:52:51 +0100, Alexander Skwar:
> Hello
> 
> I've got a variable, where seperate values are limited with a delimiter.
> Let's say PATH with : (but the question is general).
> 
> With bash, I can easily create a for loop which loops over all the
> elements, when I have bash replace the ":" with a " ", like so: ${PATH//:/
> }.
> 
> a@ubuntu-notebook:~$ echo $PATH
> /home/a/Applications/go/bin:/home/a/bin:/opt/bin:/opt/sbin:/usr/local/sbin:/usr/local/bin:/home/a/Applications/copy/x86_64:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools:/home/a/Applications/btsync:/home/a/.rvm/bin
> 
> a@ubuntu-notebook:~$ for e in ${PATH//:/ }; do echo e: $e; done
> e: /home/a/Applications/go/bin
> e: /home/a/bin
> e: /opt/bin
> e: /opt/sbin
> e: /usr/local/sbin
> e: /usr/local/bin
> e: /home/a/Applications/copy/x86_64
> e: /usr/sbin
> e: /usr/bin
> e: /sbin
> e: /bin
> e: /usr/games
> e: /usr/local/games
> e: /home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
> e: /home/a/Applications/btsync
> e: /home/a/.rvm/bin

That approach is wrong. You're replacing : with space and
invoking the split+glob operator (leaving an expansion unquoted
in ksh/bash/sh, not zsh) with the default value of IFS which
contains space, tab and newline.

That means that approach only works if $PATH components don't
contain blanks or wildcards.

Here, you could use the split+glob operator, but you want to
split on :, not blanks, and not invoke the glob part.

So it would be (bash/ksh/sh, not zsh unless in sh/ksh emulation):

IFS=: # split on :
set -f # disabe glob
for e in $PATH

However with bash/ksh/sh (not zsh), it's still wrong, because it
would discard a trailing empty component (like for a $PATH value
of /bin:/usr/bin:)


> 
> In zsh, the same for loop does not work (like it does in bash):

No, because and that's the most FAQ for zsh, leaving a variable
unquoted is not the split+glob operator (like it is in most
other shells and the number one source of bugs and security
vulnerabilities in them,
(http://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells)

In zsh, you invoke the split ($=var) or glob ($~var) operators
explicitely, so:

IFS=:
for e in $~PATH

Or use the "s" expansion flag:

for e in "${(s(:))PATH}"

For PATH however, you don't need to as zsh ties the $PATH
variable to the $path array.

So, it's just:

for e in "$path[@]"

-- 
Stephane


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: for loop with parameter expansion in zsh vs. bash
  2015-11-03  7:35 ` for loop with parameter expansion in zsh vs. bash Stephane Chazelas
@ 2015-11-03  7:47   ` Alexander Skwar
       [not found]   ` <CADn-QaNAt3y2qd4dyBnhd9ifdMOqf9DsVwFbVfdriap6uohvRA__32237.289628438$1446536964$gmane$org@mail.gmail.com>
  1 sibling, 0 replies; 6+ messages in thread
From: Alexander Skwar @ 2015-11-03  7:47 UTC (permalink / raw)
  To: zsh-users

[-- Attachment #1: Type: text/plain, Size: 3704 bytes --]

Hello

2015-11-03 8:35 GMT+01:00 Stephane Chazelas <stephane.chazelas@gmail.com>:

> 2015-11-03 07:52:51 +0100, Alexander Skwar:
> > Hello
> >
> > I've got a variable, where seperate values are limited with a delimiter.
> > Let's say PATH with : (but the question is general).
> >
> > With bash, I can easily create a for loop which loops over all the
> > elements, when I have bash replace the ":" with a " ", like so:
> ${PATH//:/
> > }.
> >
> > a@ubuntu-notebook:~$ echo $PATH
> >
> /home/a/Applications/go/bin:/home/a/bin:/opt/bin:/opt/sbin:/usr/local/sbin:/usr/local/bin:/home/a/Applications/copy/x86_64:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools:/home/a/Applications/btsync:/home/a/.rvm/bin
> >
> > a@ubuntu-notebook:~$ for e in ${PATH//:/ }; do echo e: $e; done
> > e: /home/a/Applications/go/bin
> > e: /home/a/bin
> > e: /opt/bin
> > e: /opt/sbin
> > e: /usr/local/sbin
> > e: /usr/local/bin
> > e: /home/a/Applications/copy/x86_64
> > e: /usr/sbin
> > e: /usr/bin
> > e: /sbin
> > e: /bin
> > e: /usr/games
> > e: /usr/local/games
> > e: /home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
> > e: /home/a/Applications/btsync
> > e: /home/a/.rvm/bin
>
> That approach is wrong. You're replacing : with space and
> invoking the split+glob operator (leaving an expansion unquoted
> in ksh/bash/sh, not zsh) with the default value of IFS which
> contains space, tab and newline.
>
> That means that approach only works if $PATH components don't
> contain blanks or wildcards.
>

​Well, but I do know that my variable does not contain blanks or wildcards.
As I said, PATH was just an example, so that everybody can easily follow.
So, no, ​the approach was not at all wrong. Your assumption was, though.



>
> Here, you could use the split+glob operator, but you want to
> split on :, not blanks, and not invoke the glob part.
>
> So it would be (bash/ksh/sh, not zsh unless in sh/ksh emulation):
>
> IFS=: # split on :
> set -f # disabe glob
> for e in $PATH
>

​Thanks a lot. Works great.​



>
> However with bash/ksh/sh (not zsh), it's still wrong, because it
> would discard a trailing empty component (like for a $PATH value
> of /bin:/usr/bin:)
>


​Good catch ;)​



>
>
> >
> > In zsh, the same for loop does not work (like it does in bash):
>
> No, because and that's the most FAQ for zsh, leaving a variable
> unquoted is not the split+glob operator (like it is in most
> other shells and the number one source of bugs and security
> vulnerabilities in them,
> (
> http://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells
> )
>
> In zsh, you invoke the split ($=var) or glob ($~var) operators
> explicitely, so:
>
> IFS=:
> for e in $~PATH
>
> Or use the "s" expansion flag:
>
> for e in "${(s(:))PATH}"
>
>
​Great. Thanks. Appreciated.


Too bad, that there's such a difference between the shells. Makes it hard
to share snippets with co-workers, who (for reasons, that I fail to
understand) don't use zsh.

Because of that, I'm actually using something along the lines of:

  for e in $(echo "$PATH" | tr ':' ' '); do echo e $e; done

This works everywhere.​



> For PATH however, you don't need to as zsh ties the $PATH
> variable to the $path array.
>

​I shouldn't have used PATH as an example… :/​ But thanks for the heads up
for that as well.


Alexander
-- 
=>        *Google+* => http://plus.skwar.me         <==
=> *Chat* (Jabber/Google Talk) => a.skwar@gmail.com <==

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: for loop with parameter expansion in zsh vs. bash
       [not found]   ` <CADn-QaNAt3y2qd4dyBnhd9ifdMOqf9DsVwFbVfdriap6uohvRA__32237.289628438$1446536964$gmane$org@mail.gmail.com>
@ 2015-11-03  8:47     ` Stephane Chazelas
  0 siblings, 0 replies; 6+ messages in thread
From: Stephane Chazelas @ 2015-11-03  8:47 UTC (permalink / raw)
  To: Alexander Skwar; +Cc: zsh-users

2015-11-03 08:47:42 +0100, Alexander Skwar:
[...]
> > IFS=:
> > for e in $~PATH

Sorry, meant $=PATH above ($= looks like scissors)

> > Or use the "s" expansion flag:
> >
> > for e in "${(s(:))PATH}"

Note that those don't work if $PATH is empty (which for command
search means one empty element) or unset (which for command
search means commands are looked in a default $PATH (whose value
depends on the shell)).

> ​Great. Thanks. Appreciated.
> 
> 
> Too bad, that there's such a difference between the shells. Makes it hard
> to share snippets with co-workers, who (for reasons, that I fail to
> understand) don't use zsh.
> 
> Because of that, I'm actually using something along the lines of:
> 
>   for e in $(echo "$PATH" | tr ':' ' '); do echo e $e; done
> 
> This works everywhere.​

Note that it is slightly more correct in zsh (that performs only
split upon command substitution) than in other shells that
perform split+glob. The use of echo causes problems with things
that start with - or contain backslashes though. The use of
command substitution means that trailing newlines in the last
element are trimmed.

set -o noglob; IFS=:; for e in $(printf %s "$PATH")

would be better (still a problem for trailing newlines).

Note that if you want to have zsh interpret sh code, you can use
"emulate -L zsh". The -L makes that "emulation" local to the
current function context.

So to share code between zsh and POSIX compliant shells, you
could do:

if [ "$ZSH_VERSION" ]; then
  alias 'START_SH_CODE=function { emulate -L sh'
  alias 'END_SH_CODE=} "$@"'
else
  START_SH_CODE() { :; }
  END_SH_CODE() { :; }
fi

And use as:

START_SH_CODE
IFS=:
set -f
for e in $PATH; do ...; done
END_SH_CODE

(note that it has the side effect of creating a function context
(the code is parsed as a whole, the positional parameters and
variables you declare inside are local to that block, "return"
breaks only out of that block.

(note that doing the same for bash (which by default, unless in
sh emulation is not POSIX compliant either though the
differences are a lot smaller) is a lot trickier as bash doesn't
have local scope for options or anonymous functions like zsh).

-- 
Stephane


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: for loop with parameter expansion in zsh vs. bash
  2015-11-03  7:13 ` lilydjwg
@ 2015-11-03  7:49   ` Alexander Skwar
  0 siblings, 0 replies; 6+ messages in thread
From: Alexander Skwar @ 2015-11-03  7:49 UTC (permalink / raw)
  Cc: zsh-users

[-- Attachment #1: Type: text/plain, Size: 959 bytes --]

Hello

2015-11-03 8:13 GMT+01:00 lilydjwg <lilydjwg@gmail.com>:


>
> % for e in ${=PATH//:/ }; do echo e: $e; done
>

​That's easy enough to type and remember, even for me ;)​



>
> zsh has sh_word_split option unset by default, so use ${=var} to request
> word splitting explicitely.
>
> I like this behaviour very much, because I don't need to quote nearly
> every variable :-)
>

​Thanks a lot. Might get used to it as well *G*​




>
> PS: there is a coresponding $path array, and you can tie your own
> variables like this:
>





>
> export -TU PYTHONPATH pythonpath 2>/dev/null
>
> Re-tying a variable fails for zsh 4.x so I have to redirect the error
> message to the black hole. And '-U' is for uniq to remove duplicates.
>


​Cool. Thanks ;)​


​Cheers,

Alexander
-- 
=>        *Google+* => http://plus.skwar.me         <==
=> *Chat* (Jabber/Google Talk) => a.skwar@gmail.com <==

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: for loop with parameter expansion in zsh vs. bash
  2015-11-03  6:52 Alexander Skwar
@ 2015-11-03  7:13 ` lilydjwg
  2015-11-03  7:49   ` Alexander Skwar
  0 siblings, 1 reply; 6+ messages in thread
From: lilydjwg @ 2015-11-03  7:13 UTC (permalink / raw)
  To: zsh-users

On Tue, Nov 03, 2015 at 07:52:51AM +0100, Alexander Skwar wrote:
> Hello
> 
> I've got a variable, where seperate values are limited with a delimiter.
> Let's say PATH with : (but the question is general).
> 
> With bash, I can easily create a for loop which loops over all the
> elements, when I have bash replace the ":" with a " ", like so: ${PATH//:/
> }.
> 
> a@ubuntu-notebook:~$ echo $PATH
> /home/a/Applications/go/bin:/home/a/bin:/opt/bin:/opt/sbin:/usr/local/sbin:/usr/local/bin:/home/a/Applications/copy/x86_64:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools:/home/a/Applications/btsync:/home/a/.rvm/bin
> 
> a@ubuntu-notebook:~$ for e in ${PATH//:/ }; do echo e: $e; done
> e: /home/a/Applications/go/bin
> e: /home/a/bin
> e: /opt/bin
> e: /opt/sbin
> e: /usr/local/sbin
> e: /usr/local/bin
> e: /home/a/Applications/copy/x86_64
> e: /usr/sbin
> e: /usr/bin
> e: /sbin
> e: /bin
> e: /usr/games
> e: /usr/local/games
> e: /home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
> e: /home/a/Applications/btsync
> e: /home/a/.rvm/bin
> 
> 
> In zsh, the same for loop does not work (like it does in bash):
> 
> 7:51% for e in ${PATH//:/ }; do echo e: $e; done
> e: /home/a/Applications/go/bin /home/a/bin /opt/bin /opt/sbin
> /usr/local/sbin /usr/local/bin /home/a/Applications/copy/x86_64 /usr/sbin
> /usr/bin /sbin /bin /usr/games /usr/local/games
> /home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
> /home/a/Applications/btsync
> 
> 
> As you can see there, zsh had only 1 iteration of the for loop.
> 
> What would be the zsh way to loop over elements, which are delimited by a
> ":" (or "," or ";" or whatever)?
> 
> Thanks a lot,

% for e in ${=PATH//:/ }; do echo e: $e; done

zsh has sh_word_split option unset by default, so use ${=var} to request
word splitting explicitely.

I like this behaviour very much, because I don't need to quote nearly
every variable :-)

PS: there is a coresponding $path array, and you can tie your own variables like this:

export -TU PYTHONPATH pythonpath 2>/dev/null

Re-tying a variable fails for zsh 4.x so I have to redirect the error
message to the black hole. And '-U' is for uniq to remove duplicates.

-- 
Best regards,
lilydjwg


^ permalink raw reply	[flat|nested] 6+ messages in thread

* for loop with parameter expansion in zsh vs. bash
@ 2015-11-03  6:52 Alexander Skwar
  2015-11-03  7:13 ` lilydjwg
  0 siblings, 1 reply; 6+ messages in thread
From: Alexander Skwar @ 2015-11-03  6:52 UTC (permalink / raw)
  To: zsh-users

[-- Attachment #1: Type: text/plain, Size: 1769 bytes --]

Hello

I've got a variable, where seperate values are limited with a delimiter.
Let's say PATH with : (but the question is general).

With bash, I can easily create a for loop which loops over all the
elements, when I have bash replace the ":" with a " ", like so: ${PATH//:/
}.

a@ubuntu-notebook:~$ echo $PATH
/home/a/Applications/go/bin:/home/a/bin:/opt/bin:/opt/sbin:/usr/local/sbin:/usr/local/bin:/home/a/Applications/copy/x86_64:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools:/home/a/Applications/btsync:/home/a/.rvm/bin

a@ubuntu-notebook:~$ for e in ${PATH//:/ }; do echo e: $e; done
e: /home/a/Applications/go/bin
e: /home/a/bin
e: /opt/bin
e: /opt/sbin
e: /usr/local/sbin
e: /usr/local/bin
e: /home/a/Applications/copy/x86_64
e: /usr/sbin
e: /usr/bin
e: /sbin
e: /bin
e: /usr/games
e: /usr/local/games
e: /home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
e: /home/a/Applications/btsync
e: /home/a/.rvm/bin


In zsh, the same for loop does not work (like it does in bash):

7:51% for e in ${PATH//:/ }; do echo e: $e; done
e: /home/a/Applications/go/bin /home/a/bin /opt/bin /opt/sbin
/usr/local/sbin /usr/local/bin /home/a/Applications/copy/x86_64 /usr/sbin
/usr/bin /sbin /bin /usr/games /usr/local/games
/home/a/Applications/adt-bundle-linux-x86_64/sdk/platform-tools
/home/a/Applications/btsync


As you can see there, zsh had only 1 iteration of the for loop.

What would be the zsh way to loop over elements, which are delimited by a
":" (or "," or ";" or whatever)?

Thanks a lot,



Alexander
-- 
=>        *Google+* => http://plus.skwar.me         <==
=> *Chat* (Jabber/Google Talk) => a.skwar@gmail.com <==

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-11-03  8:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <CADn-QaM6ZEP32i+6t_DC4bA8iJF_VR5hVsP6bMyxqMATxazVCw__45247.5188104019$1446533685$gmane$org@mail.gmail.com>
2015-11-03  7:35 ` for loop with parameter expansion in zsh vs. bash Stephane Chazelas
2015-11-03  7:47   ` Alexander Skwar
     [not found]   ` <CADn-QaNAt3y2qd4dyBnhd9ifdMOqf9DsVwFbVfdriap6uohvRA__32237.289628438$1446536964$gmane$org@mail.gmail.com>
2015-11-03  8:47     ` Stephane Chazelas
2015-11-03  6:52 Alexander Skwar
2015-11-03  7:13 ` lilydjwg
2015-11-03  7:49   ` Alexander Skwar

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).