Как прочитать файл в переменную в оболочке?
Я хочу прочитать файл и сохранить его в переменной, но мне нужно сохранить переменную, а не просто распечатать файл. Как я могу это сделать? Я написал этот скрипт, но это не совсем то, что мне было нужно:
#!/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 - Подстановка команд - Специальности.
Две важные ловушки
которые были проигнорированы другими ответами до сих пор:
- Удаление завершающего перевода строки из расширения команды
- Удаление символов 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
С 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