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

Я хочу прочитать файл и сохранить его в переменной, но мне нужно сохранить переменную, а не просто распечатать файл. Как я могу это сделать? Я написал этот скрипт, но это не совсем то, что мне было нужно:

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

В моем сценарии я могу указать имя файла в качестве параметра, поэтому, если файл содержит, например, "aaaa", он выведет следующее:

aaaa
11111-----

Но это просто выводит файл на экран, и я хочу сохранить его в переменной! Есть простой способ сделать это?

9 ответов

Решение

В кросс-платформенном наименьшем знаменателе sh ты используешь:

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

В bash или же zsh, чтобы прочитать весь файл в переменную без вызова cat:

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

Вызов cat в bash или же zsh украсть файл будет считаться бесполезным использованием кошки.

Обратите внимание, что нет необходимости заключать команду в кавычки, чтобы сохранить переводы строки.

Смотрите: Bash Hacker's Wiki - Подстановка команд - Специальности.

Две важные ловушки

которые были проигнорированы другими ответами до сих пор:

  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.

Если вы хотите прочитать весь файл в переменную:

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

Если вы хотите прочитать это построчно:

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

Это работает для меня: v=$(cat <file_path>) echo $v

С bash вы можете использовать read как это:

#!/usr/bin/env bash

{ IFS= read -rd '' value <config.txt;} 2>/dev/null

printf '%s' "$value"

Заметить, что:

  • Последняя строка новой строки сохраняется.

  • В stderr заставляют замолчать /dev/null путем перенаправления всего блока команд, но статус возврата команды чтения сохраняется, если это необходимо для обработки условий ошибки чтения.

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

Мой подход сейчас использует 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

Я использую:

      NGINX_PID=`cat -s "/sdcard/server/nginx/logs/nginx.pid" 2>/dev/null`

if [ "$NGINX_PID" = "" ]; then
  echo "..."
  exit
fi

Все приведенные решения довольно медленные, поэтому:

      mapfile -d '' content </etc/passwd  # Read file into an array
content="${content[*]%$'\n'}"       # Remove trailing newline

Было бы неплохо оптимизировать его еще больше, но я не могу придумать многого.

Обновление: найден более быстрый способ

      read -rd '' content </etc/passwd

Это вернет код выхода из1так что если вам нужно, чтобы это было всегда0:

      read -rd '' content </etc/passwd || :

Вы можете получить доступ к 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
Другие вопросы по тегам