从 Bash 中的文本文件创建数组

一个脚本接收一个 URL,解析它所需的字段,并将其输出重定向到一个文件 File.txt中保存。每次找到一个字段时,输出都保存在一个新行上。

File.txt

A Cat
A Dog
A Mouse
etc...

我想用 file.txt在一个新的脚本中创建一个数组,其中每一行都是数组中自己的字符串变量。到目前为止,我已经试过了:

#!/bin/bash


filename=file.txt
declare -a myArray
myArray=(`cat "$filename"`)


for (( i = 0 ; i < 9 ; i++))
do
echo "Element [$i]: ${myArray[$i]}"
done

当我运行这个脚本时,空格导致单词被拆分,而不是获取

预期输出

Element [0]: A Cat
Element [1]: A Dog
etc...

最后我得到了这个:

实际输出

Element [0]: A
Element [1]: Cat
Element [2]: A
Element [3]: Dog
etc...

如何调整下面的循环,使每行上的整个字符串与数组中的每个变量一对一地对应?

158032 次浏览

Use the mapfile command:

mapfile -t myArray < file.txt

The error is using for -- the idiomatic way to loop over lines of a file is:

while IFS= read -r line; do echo ">>$line<<"; done < file.txt

See BashFAQ/005 for more details.

You can do this too:

oldIFS="$IFS"
IFS=$'\n' arr=($(<file))
IFS="$oldIFS"
echo "${arr[1]}" # It will print `A Dog`.

Note:

Filename expansion still occurs. For example, if there's a line with a literal * it will expand to all the files in current folder. So use it only if your file is free of this kind of scenario.

You can simply read each line from the file and assign it to an array.

#!/bin/bash
i=0
while read line
do
arr[$i]="$line"
i=$((i+1))
done < file.txt

mapfile and readarray (which are synonymous) are available in Bash version 4 and above. If you have an older version of Bash, you can use a loop to read the file into an array:

arr=()
while IFS= read -r line; do
arr+=("$line")
done < file

In case the file has an incomplete (missing newline) last line, you could use this alternative:

arr=()
while IFS= read -r line || [[ "$line" ]]; do
arr+=("$line")
done < file

Related:

Use mapfile or read -a

Always check your code using shellcheck. It will often give you the correct answer. In this case SC2207 covers reading a file that either has space separated or newline separated values into an array.

Don't do this

array=( $(mycommand) )

Files with values separated by newlines

mapfile -t array < <(mycommand)

Files with values separated by spaces

IFS=" " read -r -a array <<< "$(mycommand)"

The shellcheck page will give you the rationale why this is considered best practice.

This answer says to use

mapfile -t myArray < file.txt

I made a shim for mapfile if you want to use mapfile on bash < 4.x for whatever reason. It uses the existing mapfile command if you are on bash >= 4.x

Currently, only options -d and -t work. But that should be enough for that command above. I've only tested on macOS. On macOS Sierra 10.12.6, the system bash is 3.2.57(1)-release. So the shim can come in handy. You can also just update your bash with homebrew, build bash yourself, etc.

It uses this technique to set variables up one call stack.

Make sure set the Internal File Separator (IFS) variable to $'\n' so that it does not put each word into a new array entry.

#!/bin/bash


# move all 2020 - 2022 movies to /backup/movies
# put list into file 1 line per dir


# dirs are  "movie name (year)/"
ls | egrep 202[0-2]  > 2020_movies.txt


OLDIFS=${IFS}
  

IFS=$'\n'    #fix separator


declare -a MOVIES  # array for dir names


MOVIES=( $( cat "${1}" ) )  // load into array


for M in ${MOVIES[@]} ; do
echo "[${M}]"
if [ -d "${M}" ] ; then  # if dir name


mv -v "$M" /backup/movies/
fi


done


IFS=${OLDIFS}  # restore standard separators
# not essential as IFS reverts when script ends


#END