Как прочитать файл в переменную в оболочке?


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 Wiki - Подстановка команд - Специальности .


4
Хорошо, но это bash, а не sh; это может не соответствовать всем случаям.
Моала

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.
Деджей Клейтон

16
Для новых сценариев оболочки, таких как я, обратите внимание, что версия cat использует обратные галочки, а не одинарные кавычки! Надеюсь, это сэкономит кому-то полчаса, которые понадобились мне, чтобы понять это.
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 и содержит обратную косую черту; двойные кавычки и кавычки?
user2284570

2
Вы должны заключить в кавычки переменную в echo "$value". В противном случае оболочка выполнит токенизацию пробела и расширение подстановочного знака для значения.
tripleee

3
@ user2284570 Используйте read -rвместо просто read- всегда, если только вам не требуется странное унаследованное поведение, на которое вы намекаете.
tripleee

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

Ха, наш NUL ушел!

обходные:

  • 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
Спасибо, только это сработало для меня, потому что мне нужны были новые строки.
Джейсон Ливесей,

1
@CiroSantilli: что делать, если FILE является Config.cpp и содержит обратную косую черту; двойные кавычки и кавычки?
user2284570

@ user2284570 Я не знал, но это легко узнать S="$(printf "\\\'\"")"; echo $S. Выход: \'". Так что это работает =)
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

@CiroSantilli: на 5511 строк? Вы уверены, что нет автоматизированного способа?
user2284570

@ user2284570 Я не понимаю, где 5511 строк? Подводные камни связаны с $()расширением, мой пример показывает, что $()расширение работает с \'".
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功


2

Как отмечает Сиро Сантилли, использование подстановок команд пропустит завершающие символы новой строки. Их обходной путь, заключающийся в добавлении завершающих символов, великолепен, но после его использования в течение некоторого времени я решил, что мне нужно решение, которое вообще не использует подстановку команд.

Мой подход теперь использует readвместе со printfвстроенным -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"
}

Это поддерживает входные данные с или без завершающих строк.

Пример использования:

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

Большой! Мне просто интересно, почему бы не использовать что-то вроде _contents="${_contents}${_line}\n "сохранения новых строк?
Eenoku

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

Что ж, спасибо за объяснение!
Eenoku

-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.