问题描述:

I'm new to UNIX and have this really simple problem:

I have a text-file (input.txt) containing a string in each line. It looks like this:

House

Monkey

Car

And inside my shell script I need to read this input file line by line to get to a variable like this:

things="House,Monkey,Car"

I know this sounds easy, but I just couldnt find any simple solution for this. My closest attempt so far:

#!/bin/sh

things=""

addToString() {

things="${things},$1"

}

while read line; do addToString $line ;done <input.txt

echo $things

But this won't work. Regarding to my google research I thought the while loop would create a new sub shell, but this I was wrong there (see the comment section). Nevertheless the variable "things" was still not available in the echo later on. (I cannot just write the echo inside the while loop, because I need to work with that string later on)

Could you please help me out here? Any help will be appreciated, thank you!

网友答案:

What you proposed works fine! I've only made two changes here: Adding missing quotes, and handling the empty-string case.

things=""
addToString() {
    if [ -n "$things" ]; then
      things="${things},$1"
    else
      things="$1"
    fi
}
while read -r line; do addToString "$line"; done <input.txt
echo "$things"

If you were piping into while read, this would create a subshell, and that would eat your variables. You aren't piping -- you're doing a <input.txt redirection. No subshell, code works without changes.


That said, there are better ways to read lists of items into shell variables. On any version of bash after 3.0:

IFS=$'\n' read -r -d '' -a things <input.txt  # read into an array
printf -v things_str '%s,' "${things[@]}"     # write array to a comma-separated string
echo "${things_str%,}"                        # print that string w/o trailing comma

...on bash 4, that first line can be:

readarray -t things <input.txt # read into an array
网友答案:

This is not a shell solution, but the truth is that solutions in pure shell are often excessively long and verbose. So e.g. to do string processing it is better to use special tools that are part of the “default” Unix environment.

sed ':b;N;$!bb;s/\n/,/g' < input.txt

If you want to omit empty lines, then:

sed ':b;N;$!bb;s/\n\n*/,/g' < input.txt

Speaking about your solution, it should work, but you should really always use quotes where applicable. E.g. this works for me:

things=""
while read line; do things="$things,$line"; done < input.txt
echo "$things"

(Of course, there is an issue with this code, as it outputs a leading comma. If you want to skip empty lines, just add an if check.)

网友答案:

This might/might not work, depending on the shell you are using. On my Ubuntu 14.04/x64, it works with both bash and dash.

To make it more reliable and independent from the shell's behavior, you can try to put the whole block into a subshell explicitly, using the (). For example:

(
things=""
addToString() {
    things="${things},$1"
}
while read line; do addToString $line ;done 
echo $things
) < input.txt

P.S. You can use something like this to avoid the initial comma. Without bash extensions (using short-circuit logical operators instead of the if for shortness):

test -z "$things" && things="$1" || things="${things},${1}"

Or with bash extensions:

things="${things}${things:+,}${1}"

P.P.S. How I would have done it:

tr '\n' ',' < input.txt | sed 's!,$!\n!'
网友答案:

You can do this too:

#!/bin/bash
while read -r i
do
[[ $things == "" ]] && things="$i" || things="$things","$i"
done < <(grep . input.txt)
echo "$things"

Output:

House,Monkey,Car

N.B:

Used grep to tackle with empty lines and the probability of not having a new line at the end of file. (Normal while read will fail to read the last line if there is no newline at the end of file.)

相关阅读:
Top