Як прочитати файл у змінну в оболонці?


489

Я хочу прочитати файл і зберегти його у змінній, але мені потрібно зберегти змінну, а не просто роздрукувати файл. Як я можу це зробити? Я написав цей сценарій, але це не зовсім те, що мені потрібно:

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

У моєму сценарії я можу дати ім'я файлу як параметр, тому, якщо, наприклад, файл містить "aaaa", він роздрукував би це:

aaaa
11111-----

Але це просто виводить файл на екран, і я хочу зберегти його в змінну! Чи є простий спосіб це зробити?


1
Здається, це звичайний текст. Якщо це двійковий файл, ви повинні були б це , як результат catабо $(<someFile)призведе до неповного виходу (розмір менше , ніж реальний файл).
Сила Водолія

Відповіді:


1052

У крос-платформі shви використовуєте найменший найпоширеніший знаменник :

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

У bashчи zsh, щоб прочитати цілий файл у змінну без виклику cat:

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

Виклик catу файл bashабо zshйого трансляція вважається марним використанням Cat .

Зауважте, що для збереження нових рядків не потрібно цитувати заміну команди.

Дивіться: Вікі Bash Hacker's - Заміна команд - Спеціалісти .


4
Добре, але це баш, не ш; він може підходити не у всіх випадках.
моала

14
Чи не буде value="`cat config.txt`"і value="$(<config.txt)"бути безпечнішим у випадку, якщо config.txt містить пробіли?
Мартін фон Віттіх

13
Зауважте, що використання, catяк описано вище, не завжди вважається марним використанням cat. Наприклад, < invalid-file 2>/dev/nullприведе повідомлення про помилку, до якого не можна переадресувати /dev/null, тоді cat invalid-file 2>/dev/nullяк правильно перенаправляється до /dev/null.
Dejay Clayton

16
Для нових скриптових оболонок, таких як я, зауважте, у котячій версії використовуються зворотні кліщі, а не поодинокі цитати! Сподіваюся, це врятує когось за півгодини, що мені знадобилося це зрозуміти.
ericksonla

7
Для нових башерів, як я: Зауважте, що value=$(<config.txt)це добре, але value = $(<config.txt)погано. Слідкуйте за цими просторами.
ArtHare

88

Якщо ви хочете прочитати весь файл у змінній:

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

Якщо ви хочете прочитати його по черзі:

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

2
@brain: що робити, якщо файл Config.cpp і містить зворотні косої риси; подвійні цитати та цитати?
користувач2284570

2
Вам слід подвоїти цитування змінної в echo "$value". В іншому випадку оболонка буде виконувати токенізацію пробілу та розширення підстановки на значення.
трійка

3
@ user2284570 Використовуйте read -rзамість просто read- завжди, якщо ви конкретно не вимагаєте дивної спадщини, на яку ви натякаєтесь.
трійчатка

74

Дві важливі підводні камені

які досі були проігноровані іншими відповідями:

  1. Вилучення нового рядка від розширення команди
  2. Видалення символу NUL

Вилучення нового рядка від розширення команди

Це проблема для:

value="$(cat config.txt)"

типові рішення, але не для readзаснованих на рішеннях.

Розширення команд видаляє нові рядки:

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

Виходи:

0000000 61
0000001

Це порушує наївний метод читання з файлів:

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

Вирішення POSIX: додайте додатковий символ до розширення команди та видаліть його пізніше:

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

Виходи:

0000000 61 0a 0a
0000003

Майже рішення POSIX: кодування ASCII. Дивись нижче.

Видалення символу NUL

Немає здорового способу Bash зберігати символи NUL у змінних .

Це впливає і на розширення, і на readрішення, і я не знаю жодного корисного рішення для цього.

Приклад:

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

Виходи:

0000000 61 00 62
0000003

0000000 61 62
0000002

Га, наш НУЛ пішов!

Обхідні шляхи:

  • Кодування ASCII. Дивись нижче.

  • використовувати літери розширення bash $"":

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

    Працює лише для літералів, тому не корисний для читання з файлів.

Обхід підводних каменів

Зберігайте версію файлу, кодовану uuencode base64, у змінній та декодуйте перед кожним використанням:

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

Вихід:

0000000 61 00 0a
0000003

uuencode та udecode - це POSIX 7, але не є Ubuntu 12.04 за замовчуванням ( sharutilsпакет) ... Я не бачу альтернативи POSIX 7 для процесу bash<() розширення заміни крім запису до іншого файлу ...

Звичайно, це повільно і незручно, тому, мабуть, справжня відповідь: не використовуйте Bash, якщо вхідний файл може містити символи NUL.


2
Дякую лише, що цей працював на мене, бо мені потрібні були нові рядки.
Jason Livesay

1
@CiroSantilli: а що робити, якщо FILE - це Config.cpp і містить зворотні риски; подвійні цитати та цитати?
користувач2284570

@ user2284570 Я не знав, але це легко дізнатися: S="$(printf "\\\'\"")"; echo $S . Вихід: \'". Так це працює =)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@CiroSantilli: На 5511 рядках? Ви впевнені, що немає автоматизованого способу?
користувач2284570

@ user2284570 Я не розумію, де є 5511 рядків? Підводні камені виникають внаслідок $()розширення, мій приклад показує, що $()розширення працює з \'".
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


2

Як зазначає Ciro Santilli, за допомогою підстановок команд випаде задні рядки. Їхнє вирішення, що додає символи для завершення, чудово, але, використовуючи його досить довго, я вирішив, що мені потрібно рішення, яке взагалі не використовувало заміну команд.

Зараз мій підхід застосовується readразом із printfвбудованим-v прапором для того, щоб читати вміст stdin безпосередньо у змінній.

# 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"
}

Це підтримує введення даних з новими рядками або без них.

Приклад використання:

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

Чудово! Мені просто цікаво, чому б не використати щось на зразок _contents="${_contents}${_line}\n "збереження нових рядків?
Еноку

1
Ви питаєте про те $'\n'? Це необхідно, інакше ви додаєте буквальні \ та nсимволи. У вашому блоці коду також є додаткове місце в кінці, не впевнений, що це навмисно, але він буде відступати кожному наступному рядку додатковою пробілом.
dimo414

Ну, дякую за пояснення!
Еноку

-3

Ви можете отримати доступ до 1 рядка одночасно за допомогою циклу

#!/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

Скопіюйте весь вміст у файл (скажімо line.sh); Виконати

chmod +x line.sh
./line.sh

Ваша forпетля не перетворюється на рядки, вона перетворюється на слова. У випадку /etc/passwd, буває, що кожен рядок містить лише одне слово. Однак інші файли можуть містити кілька слів на рядок.
mpb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.