Handle special characters in bash for…in loop

2019-02-18 21:54发布

Suppose I've got a list of files

file1
"file 1"
file2

a for...in loop breaks it up between whitespace, not newlines:

for x in $( ls ); do
   echo $x
done

results:

file
1
file1
file2

I want to execute a command on each file. "file" and "1" above are not actual files. How can I do that if the filenames contains things like spaces or commas?

It's a little trickier than I think find -print0 | xargs -0 could handle, because I actually want the command to be something like "convert input/file1.jpg .... output/file1.jpg" so I need to permutate the filename in the process.

3条回答
SAY GOODBYE
2楼-- · 2019-02-18 22:04

I know this one is LONG past "answered", and with all due respect to eduffy, I came up with a better way and I thought I'd share it.

What's "wrong" with eduffy's answer isn't that it's wrong, but that it imposes what for me is a painful limitation: there's an implied creation of a subshell when the output of the ls is piped and this means that variables set inside the loop are lost after the loop exits. Thus, if you want to write some more sophisticated code, you have a pain in the buttocks to deal with.

My solution was to take the "readline" function and write a program out of it in which you can specify any specific line number that you may want that results from any given function call. ... As a simple example, starting with eduffy's:

ls_output=$(ls -1)
# The cut at the end of the following line removes any trailing new line character
declare -i line_count=$(echo "$ls_output" | wc -l | cut -d ' ' -f 1)
declare -i cur_line=1 
while [ $cur_line -le $line_count ] ;
do  
   # NONE of the values in the variables inside this do loop are trapped here.
   filename=$(echo "$ls_output" | readline -n $cur_line)
   # Now line contains a filename from the preceeding ls command
   cur_line=cur_line+1
done

Now you have wrapped up all the subshell activity into neat little contained packages and can go about your shell coding without having to worry about the scope of your variable values getting trapped in subshells.

I wrote my version of readline in gnuc if anyone wants a copy, it's a little big to post here, but maybe we can find a way...

Hope this helps, RT

查看更多
对你真心纯属浪费
3楼-- · 2019-02-18 22:09

UPDATE BY OP: this answer sucks and shouldn't be on top ... @Jordan's post below should be the accepted answer.

one possible way:

ls -1 | while read x; do
   echo $x
done

查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-02-18 22:19

Actually, Mark's suggestion works fine without even doing anything to the internal field separator. The problem is running ls in a subshell, whether by backticks or $( ) causes the for loop to be unable to distinguish between spaces in names. Simply using

for f in *

instead of the ls solves the problem.

#!/bin/bash
for f in *
do
 echo "$f"
done
查看更多
登录 后发表回答