Làm thế nào để đọc một tập tin vào một biến trong shell?


489

Tôi muốn đọc một tệp và lưu nó trong biến, nhưng tôi cần giữ biến đó và không chỉ in ra tệp. Tôi có thể làm cái này như thế nào? Tôi đã viết kịch bản này nhưng nó không hoàn toàn là những gì tôi cần:

#!/bin/sh
while read LINE  
do  
  echo $LINE  
done <$1  
echo 11111-----------  
echo $LINE  

Trong tập lệnh của tôi, tôi có thể đặt tên tệp làm tham số, vì vậy, nếu tệp chứa "aaaa", chẳng hạn, nó sẽ in ra điều này:

aaaa
11111-----

Nhưng điều này chỉ in ra các tập tin trên màn hình, và tôi muốn lưu nó vào một biến! Có cách nào làm dễ hơn không?


1
Nó có vẻ là một văn bản đơn giản. Nếu đó là một tệp nhị phân, bạn sẽ cần điều này , do kết quả của cathoặc $(<someFile)sẽ dẫn đến một đầu ra không đầy đủ (kích thước nhỏ hơn tệp thực).
Sức mạnh Bảo Bình

Câu trả lời:


1052

Trong đa nền tảng, mẫu số chung thấp nhất shbạn sử dụng:

#!/bin/sh
value=`cat config.txt`
echo "$value"

Trong bashhoặc zsh, để đọc toàn bộ tệp thành một biến mà không cần gọi cat:

#!/bin/bash
value=$(<config.txt)
echo "$value"

Gọi catvào bashhoặc zshđể nhét một tập tin sẽ được coi là Sử dụng Mèo vô dụng .

Lưu ý rằng không cần thiết phải trích dẫn thay thế lệnh để duy trì dòng mới.

Xem: Wiki của Bash Hacker - Thay thế lệnh - Đặc sản .


4
Ok nhưng đó là bash, không phải sh; nó có thể không phù hợp với tất cả các trường hợp.
moala

14
Sẽ không value="`cat config.txt`"value="$(<config.txt)"an toàn hơn trong trường hợp config.txt chứa khoảng trắng?
Martin von Wittich

13
Lưu ý rằng sử dụng catnhư trên không phải lúc nào cũng được coi là sử dụng vô ích cat. Ví dụ: < invalid-file 2>/dev/nullsẽ dẫn đến một thông báo lỗi không thể được định tuyến đến /dev/null, trong khi cat invalid-file 2>/dev/nullđó được định tuyến đúng /dev/null.
Dejay Clayton

16
Đối với những người viết kịch bản shell mới như tôi, lưu ý phiên bản mèo sử dụng dấu tick ngược, không phải dấu ngoặc đơn! Hy vọng rằng điều này sẽ giúp ai đó tiết kiệm được nửa giờ để tôi tìm ra nó.
ericksonla

7
Đối với những người mới sử dụng như tôi: Lưu ý value=$(<config.txt)là tốt, nhưng value = $(<config.txt)là xấu. Coi chừng những không gian đó
ArtHare

88

Nếu bạn muốn đọc toàn bộ tập tin thành một biến:

#!/bin/bash
value=`cat sources.xml`
echo $value

Nếu bạn muốn đọc từng dòng một:

while read line; do    
    echo $line    
done < file.txt

2
@brain: Điều gì xảy ra nếu tệp là Config.cpp và chứa dấu gạch chéo ngược; báo giá kép và báo giá?
dùng2284570

2
Bạn nên trích dẫn hai biến trong echo "$value". Mặt khác, shell sẽ thực hiện mã thông báo khoảng trắng và mở rộng ký tự đại diện trên giá trị.
tripleee 4/2/2016

3
@ user2284570 Sử dụng read -rthay vì chỉ read- luôn luôn, trừ khi bạn đặc biệt yêu cầu hành vi di sản kỳ lạ mà bạn đang ám chỉ.
tripleee 4/2/2016

74

Hai cạm bẫy quan trọng

mà đã bị bỏ qua bởi các câu trả lời khác cho đến nay:

  1. Trailing loại bỏ dòng mới từ mở rộng lệnh
  2. Xóa ký tự NUL

Trailing loại bỏ dòng mới từ mở rộng lệnh

Đây là một vấn đề cho:

value="$(cat config.txt)"

loại giải pháp, nhưng không cho readcác giải pháp dựa trên.

Mở rộng lệnh loại bỏ các dòng mới:

S="$(printf "a\n")"
printf "$S" | od -tx1

Đầu ra:

0000000 61
0000001

Điều này phá vỡ phương pháp đọc ngây thơ từ các tập tin:

FILE="$(mktemp)"
printf "a\n\n" > "$FILE"
S="$(<"$FILE")"
printf "$S" | od -tx1
rm "$FILE"

Giải pháp thay thế POSIX: nối thêm char vào phần mở rộng lệnh và xóa nó sau:

S="$(cat $FILE; printf a)"
S="${S%a}"
printf "$S" | od -tx1

Đầu ra:

0000000 61 0a 0a
0000003

Hầu như cách giải quyết POSIX: mã hóa ASCII. Xem bên dưới.

Xóa ký tự NUL

Không có cách Bash lành mạnh để lưu trữ các ký tự NUL trong các biến .

Điều này ảnh hưởng đến cả mở rộng và readgiải pháp và tôi không biết cách giải quyết tốt nào cho nó.

Thí dụ:

printf "a\0b" | od -tx1
S="$(printf "a\0b")"
printf "$S" | od -tx1

Đầu ra:

0000000 61 00 62
0000003

0000000 61 62
0000002

Ha, NUL của chúng ta đã biến mất!

Cách giải quyết:

  • Mã hóa ASCII. Xem bên dưới.

  • sử dụng bash mở rộng bằng $""chữ:

    S=$"a\0b"
    printf "$S" | od -tx1

    Chỉ hoạt động cho chữ, vì vậy không hữu ích để đọc từ các tập tin.

Cách giải quyết cho những cạm bẫy

Lưu trữ một phiên bản mã hóa uuencode base64 của biến trong biến và giải mã trước mỗi lần sử dụng:

FILE="$(mktemp)"
printf "a\0\n" > "$FILE"
S="$(uuencode -m "$FILE" /dev/stdout)"
uudecode -o /dev/stdout <(printf "$S") | od -tx1
rm "$FILE"

Đầu ra:

0000000 61 00 0a
0000003

uuencode và udecode là POSIX 7 nhưng không có trong Ubuntu 12.04 theo mặc định ( sharutilsgói) ... Tôi không thấy một thay thế POSIX 7 cho <()phần mở rộng thay thế quy trình bash trừ việc ghi vào tệp khác ...

Tất nhiên, điều này chậm và bất tiện, vì vậy tôi đoán câu trả lời thực sự là: không sử dụng Bash nếu tệp đầu vào có thể chứa các ký tự NUL.


2
Cảm ơn chỉ có điều này làm việc cho tôi vì tôi cần dòng mới.
Jason Livesay

1
@CiroSantilli: Sẽ thế nào nếu FILE là Config.cpp và chứa dấu gạch chéo ngược; báo giá kép và báo giá?
dùng2284570

@ user2284570 Tôi không biết, nhưng thật dễ để tìm hiểu : S="$(printf "\\\'\"")"; echo $S. Đầu ra : \'". Vì vậy, nó hoạt động =)
Ciro Santilli 郝海东 冠状 病 六四 事件

@CiroSantilli: Trên 5511 dòng? Bạn có chắc chắn không có cách tự động?
dùng2284570

@ user2284570 Tôi không hiểu, ở đâu có 5511 dòng? Những cạm bẫy đến từ việc $()mở rộng, ví dụ của tôi cho thấy việc $()mở rộng hoạt động với \'".
Ciro Santilli 郝海东 冠状 病 事件


2

Như Ciro Santilli lưu ý sử dụng thay thế lệnh sẽ bỏ dòng mới. Cách giải quyết của họ thêm các ký tự dấu là rất tốt, nhưng sau khi sử dụng nó một thời gian, tôi quyết định tôi cần một giải pháp không sử dụng thay thế lệnh nào cả.

Cách tiếp cận của tôi hiện sử dụng readcùng với cờprintf dựng sẵn để đọc nội dung của stdin trực tiếp vào một biến.-v

# Reads stdin into a variable, accounting for trailing newlines. Avoids needing a subshell or
# command substitution.
read_input() {
  # Use unusual variable names to avoid colliding with a variable name
  # the user might pass in (notably "contents")
  : "${1:?Must provide a variable to read into}"
  if [[ "$1" == '_line' || "$1" == '_contents' ]]; then
    echo "Cannot store contents to $1, use a different name." >&2
    return 1
  fi

  local _line _contents
   while read -r _line; do
     _contents="${_contents}${_line}"$'\n'
   done
   _contents="${_contents}${_line}" # capture any content after the last newline
   printf -v "$1" '%s' "$_contents"
}

Điều này hỗ trợ đầu vào có hoặc không có dòng mới.

Ví dụ sử dụng:

$ read_input file_contents < /tmp/file
# $file_contents now contains the contents of /tmp/file

Tuyệt quá! Tôi chỉ tự hỏi, tại sao không sử dụng một cái gì đó như _contents="${_contents}${_line}\n "để duy trì dòng mới?
Eenoku

1
Bạn đang hỏi về $'\n'? Điều đó là cần thiết, nếu không, bạn đang nối thêm chữ \ nký tự. Khối mã của bạn cũng có thêm một khoảng trắng ở cuối, không chắc đó có phải là chủ ý hay không, nhưng nó sẽ thụt vào mỗi dòng tiếp theo với một khoảng trắng thừa.
dimo414

Vâng, cảm ơn bạn đã giải thích!
Eenoku

-3

Bạn có thể truy cập 1 dòng một lần bằng vòng lặp

#!/bin/bash -eu

#This script prints contents of /etc/passwd line by line

FILENAME='/etc/passwd'
I=0
for LN in $(cat $FILENAME)
do
    echo "Line number $((I++)) -->  $LN"
done

Sao chép toàn bộ nội dung vào Tệp (nói line.sh); Hành hình

chmod +x line.sh
./line.sh

Bạn forvòng lặp không thực hiện vòng lặp qua đường dây, nó vòng qua lời nói. Trong trường hợp /etc/passwd, mỗi dòng chỉ chứa một từ. Tuy nhiên, các tệp khác có thể chứa nhiều từ trên mỗi dòng.
mpb
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.