ZSH for loop array variable issue
Asked Answered
R

4

39

I'm working in , but I'm sure that instructions will also be helpful.

I need to have a for loop that goes through the values stored in the array lw and then launches a shell script, based on the name value stored in the array.

So far, this is what I've come up with:

$lw=('plugin1' 'plugin2' 'plugin3')

for i in $lw;
  do . ~/Library/Rogall/plugins/$lw[$i]/lw.prg end;
done

Running this gives me an error saying that it can't find ~/Library/Rogall/plugins//lw.prg. It appears as if it's ignoring my variable all together.

Any ideas where I've messed up?

Receptacle answered 4/6, 2012 at 20:3 Comment(1)
Try echo $i to see what it contains.Bosomy
A
68

It's actually much simpler than that:

lw=('plugin1' 'plugin2' 'plugin3')

for i in $lw; do
  . ~/Library/Rogall/plugins/$i/lw.prg end
done

In summary:

  • Assign to foo, not $foo (the shell would try to expand $foo and assign to whatever it expands to; typically not useful)
  • Use the loop variable directly; it contains the array value rather than the index
Adachi answered 4/6, 2012 at 20:12 Comment(4)
That makes so much sense! Thank you!Receptacle
Or just for i ($lw) . ~/Library/Rogall/plugins/$i/lw.prg endPeptone
Not that in the general case, for i in $array would skip the empty elements. You'd need for i in "$array[@]" or for i in "${(@)array}" to preserve the empty ones (or the ksh/bash-compatible syntax: for i in "${array[@]}").Peptone
@theonlygusti you mean $lw? Simply put in the ('plugin1' 'plugin2' 'plugin3') directly in place of $lw.Peroxidase
C
31

Why bother using the array? This can be done in portable sh very easily:

lw='plugin1 plugin2 plugin3'

for i in $lw;
  do . ~/Library/Rogall/plugins/$i/lw.prg end
done

Note that for this to work in zsh, you need to make zsh do the right thing with: set -o shwordsplit

Czechoslovakia answered 4/6, 2012 at 20:31 Comment(8)
Not everything is about being as portable as can be, especially if it's to be run on a Mac (judging by the directory names). Also your solution fails for the generic case in which there might be spaces in each word.Fitter
@Moritz This is not a generic case, but a specific case in which there are no spaces in the filenames. Portability may not always be necessary, but it is always a desirable goal. And this solution works just fine on a Mac.Czechoslovakia
@Moritz And spaces in filenames are an abomination! Also, Jan's solution fails if there is any whitespace in the filename.Czechoslovakia
How do you know that the plugin directories don't contain spaces? The names presented in the original post are so generic that it's safe to assume that they're not the actual names. Jan's solution will work as it is on zsh with certain settings. Even if not it can be made with two trivial changes. Your solution cannot be used with white spaces easily. But alas, things like "spaces in file names are an abomination" is so incredibly up for debate that it's not even funny. But let's stop here and agree to disagree.Fitter
Note that it doesn't work with zsh where you'd need to use for i in $=lw to explicitely request the split operator and in other shells invokes the split+glob operator at the time the variable is expanded, so it depends on the current value of $IFS and the state of the noglob option at the time of expansion.Peptone
It isn't portable if it relies on the ZSH defaults being changed when the context is likely a .zshrc file. Array is a better solution.Juliettajuliette
shwordsplit fixed things for me!Albie
"portable" means that it works in any properly behaving shell. The fact that zsh needs to be modified to behave properly says nothing about the portability of this code.Czechoslovakia
T
11

I had a problem where a loop like this one

for i in (1 2 3 4); do echo $i; done

was repeatedly not working as I wanted. I would get errors like zsh: unknown file attribute: 1, or outputs like

1 2 3 4

no matter how I re-contorted it, instead of my desired

1
2
3
4

To get my desired behaviour I had to remove the in keyword from the loop definition

for i (1 2 3 4); do echo $i; done
1
2
3
4
Theodoretheodoric answered 31/3, 2020 at 11:59 Comment(0)
B
1

For others who end up here wrestling with shell scripts, arrays and spaces in filenames...... like my file list below;

$ ls *.meta
20160324 1850 - ABC - Clarke And Dawe.ts.meta
20160706 1515 - 9Gem - Agatha Christie's Poirot.ts.meta
20181213 0155 - 10 BOLD - Star Trek_ The Next Generation.ts.meta
20210424 1927 - ABCTV HD - The Durrells.ts.meta
20210501 1927 - ABCTV HD - The Durrells.ts.meta
20210818 1930 - SBS ONE HD - Tony Robinson's World By Rail.ts.meta

After a lot of searching and trying different approaches this worked out to be the least problematic and worked on MacOS 10.15

eval "list=( $(find . -name "*.ts" -exec echo \"{}\" \;) )"
for name in "${list[@]}"
do
res=`mediainfo --Output=''Video\;\%Width\%:%Height\%'' "$name"`
echo ${name} :: $res
sed -i .bak -e 5s/\;[[:digit:]]*:[[:digit:]]*// -e 5s/$/\;$res/ "$name.meta" 
done

Ignoring the internals of what I was trying to achieve, the critical aspects were using eval and wrapping double quotes around the filename in the echo within the find. (What was I doing? I was pulling the resolution out of Enigma2 PVR recordings and writing it into line 5 of the related meta data file)

Thanks to all those who posted their own knowledge on the various issues that shell scripting generates.

Brooksbrookshire answered 14/10, 2021 at 23:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.