8

How do I iterate over all the lines output by a command using zsh, without setting IFS?

The reason is that I want to run a command against every file output by a command, and some of these files contain spaces.

Eg, given the deleted file:

foo/bar baz/gamma 

That is, a single directory 'foo', containing a sub directory 'bar baz', containing a file 'gamma'.

Then running:

git ls-files --deleted | xargs ls 

Will report in that file being handled as two files: 'foo/bar', and '/baz/gamma'.

I need it to handle it as one file: 'foo/bar baz/gamma'.

3
  • does your system have the xargs command? Commented Oct 13, 2011 at 6:54
  • @lunixboches: Yes, it does - however, it still fails to handle the lines properly. Commented Oct 13, 2011 at 6:58
  • 3
    git ls-files -z | xargs -0 ls will use null (\0) separators instead of whitespace Commented Oct 13, 2011 at 7:19

2 Answers 2

15

If you want to run the command once for all the lines:

ls "${(@f)$(git ls-files --deleted)}" 

The f parameter expansion flag means to split the command's output on newlines. There's a more general form (@s:||:) to split at an arbitrary string like ||. The @ flag means to retain empty records. Somewhat confusingly, the whole expansion needs to be inside double quotes, to avoid IFS splitting on the output of the command substitution, but it will produce separate words for each record.

If you want to run the command for each line in turn, the portable idiom isn't particularly complicated:

git ls-filed --deleted | while IFS= read -r line; do ls $line; done 

If you want to run the command as few times as the command line length limit permits, use zargs.

autoload -U zargs zargs -- "${(@f)$(git ls-files --deleted)}" -- ls 
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for that - there's lots of useful information here but I think xargs is still more efficient because it will run one command for each group of files. (What if I have so many files that won't fit on the args list?)
@Arafangion zargs is like xargs (runs as few instances as possible while fitting inside the command line length limit), only with a reasonable interface.
6

Using tr and the -0 option of xargs, assuming that the lines don't contain \000 (NUL), which is a fair assumption due to NUL being one of the characters that can't appear in filenames:

git ls-files --deleted | tr '\n' '\000' | xargs -0 ls 

this turns the line: foo/bar baz/gamma\n into foo/bar baz/gamma\000 which xargs -0 knows how to handle

1 Comment

Can't get better than that, thanks! (And it works in bash, too!)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.