Kabuktaki bir değişkene dosya nasıl okunur?


489

Bir dosyayı okumak ve değişkene kaydetmek istiyorum, ancak değişkeni tutmam ve sadece dosyayı yazdırmamam gerekiyor. Bunu nasıl yapabilirim? Bu senaryoyu yazdım ama tam olarak ihtiyacım olan şey değil:

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

Benim komut dosyasında, parametre olarak dosya adını verebilir, bu nedenle, dosya "aaaa" içeriyorsa, örneğin, bu çıktı:

aaaa
11111-----

Ama bu sadece dosyayı ekrana yazdırıyor ve bir değişkene kaydetmek istiyorum! Bunu yapmanın kolay bir yolu var mı?


1
Düz bir metin gibi görünüyor. Bir ikili dosya olsaydı, gerekir bu sonucu, catya $(<someFile)(boyut gerçek dosyadan daha azdır) tamamlanmamış bir çıkış ile sonuçlanacaktır.
Kova Gücü

Yanıtlar:


1052

Kullandığınız platformlar arası, en düşük ortak paydada sh:

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

Gelen bashveya zsh, yürütmesini olmadan bir değişken içine tüm dosyayı okumak için cat:

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

Çağırma catiçinde bashveya zshbir dosyayı slurp bir düşünülebilir Cat Yararsız Kullanımı .

Yeni satırları korumak için komut ikamesini belirtmenin gerekli olmadığını unutmayın.

Bakınız: Bash Hacker'ın Wiki'si - Komuta ikamesi - Uzmanlıklar .


4
Tamam ama bash, sh değil; tüm durumlara uymayabilir.
moala

14
Olmaz value="`cat config.txt`"ve value="$(<config.txt)"config.txt boşluk içeriyorsa bu durumda daha güvenli?
Martin von Wittich

13
catYukarıdaki gibi kullanmanın her zaman yararsız bir kullanım olarak görülmediğini unutmayın cat. Örneğin, < invalid-file 2>/dev/nullyönlendirilebilir edilemez bir hata iletisine neden olur /dev/nulloysa cat invalid-file 2>/dev/nulldüzgün yönlendirilir olsun demek /dev/null.
Dejay Clayton

16
Benim gibi yeni kabuk komut dosyaları için, kedi versiyonunun tek tırnak değil, geri keneler kullandığını unutmayın! İnşallah bu birini yarım saat kurtaracak, anlaması beni aldı.
ericksonla

7
Benim gibi yeni bashers için: Bunun value=$(<config.txt)iyi, ama value = $(<config.txt)kötü olduğunu unutmayın. Bu alanlara dikkat edin.
ArtHare

88

Dosyanın tamamını bir değişkene okumak istiyorsanız:

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

Eğer satır satır okumak istiyorsanız:

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

2
@brain: Dosya Config.cpp ise ve ters eğik çizgi içeriyorsa; çift ​​tırnak ve tırnak?
user2284570

2
İçindeki değişkeni iki kez tırnak içine almalısınız echo "$value". Aksi takdirde, kabuk, değer üzerinde boşluk belirteci ve joker karakter genişletmesi gerçekleştirir.
tripleee

3
@ user2284570 Özellikle bahsettiğiniz tuhaf eski davranışı gerektirmedikçe, read -rsadece read- her zaman yerine kullanın .
tripleee

74

İki önemli tuzak

Şimdiye kadar başka cevaplar tarafından göz ardı edildi:

  1. Komut genişletmesinden son satırsonu kaldırma işlemi
  2. NUL karakteri kaldırma

Komut genişletmesinden son satırsonu kaldırma işlemi

Bu, aşağıdakiler için bir sorundur:

value="$(cat config.txt)"

tipi çözümler, ancak readtabanlı çözümler için değil .

Komut genişletme, takip eden yeni satırları kaldırır:

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

Çıktılar:

0000000 61
0000001

Bu, dosyalardan naif okuma yöntemini kırar:

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

POSIX geçici çözümü: komut genişletmesine fazladan bir karakter ekleyin ve daha sonra kaldırın:

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

Çıktılar:

0000000 61 0a 0a
0000003

Neredeyse POSIX geçici çözümü: ASCII kodlaması. Aşağıya bakınız.

NUL karakteri kaldırma

NUL karakterlerini değişkenlerde saklamanın aklı başında bir Bash yolu yoktur .

Bu hem genişlemeyi hem de readçözümleri etkiler ve bunun için iyi bir çözüm bilmiyorum.

Misal:

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

Çıktılar:

0000000 61 00 62
0000003

0000000 61 62
0000002

Ha, NUL'umuz gitti!

Geçici Çözümler:

  • ASCII kodlaması. Aşağıya bakınız.

  • bash uzantısı $""değişmezlerini kullanın :

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

    Yalnızca değişmez değerler için çalışır, bu nedenle dosyalardan okumak için yararlı değildir.

Tuzaklar için geçici çözüm

Bir uuencode base64 kodlu sürümünü değişkente saklayın ve her kullanımdan önce kodunu çözün:

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

Çıktı:

0000000 61 00 0a
0000003

uuencode ve udecode POSIX 7'dir ancak varsayılan olarak Ubuntu 12.04'te değildir ( sharutilspaket) ... <()Başka bir dosyaya yazmak dışında bash işlemi ikame uzantısı için POSIX 7 alternatifini görmüyorum ...

Tabii ki, bu yavaş ve elverişsiz, bu yüzden gerçek cevap: giriş dosyası NUL karakterler içeriyorsa Bash kullanmayın.


2
Teşekkürler sadece benim için çalıştı çünkü yeni satırlara ihtiyacım vardı.
Jason Livesay

1
@CiroSantilli: DOSYA Config.cpp ise ve ters eğik çizgi içeriyorsa; çift ​​tırnak ve tırnak?
user2284570

@ user2284570 Bilmiyordum, ama bulmak kolaydır: S="$(printf "\\\'\"")"; echo $S. Çıktı: \'". Yani işe yarıyor =)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

@CiroSantilli: 5511 hatta mı? Otomatik bir yol olmadığından emin misiniz?
user2284570

@ user2284570 Anlamıyorum, 5511 satır nerede? Tuzaklar $()genişlemeden geliyor, örneğim $()genişlemenin birlikte çalıştığını gösteriyor \'".
Ciro Santilli


2

Ciro Santilli'nin komut ikamelerini kullandığını belirttiği gibi , son satırları bırakacaktır. Sondaki karakterleri eklemek onların çözümü harika, ama oldukça uzun bir süre kullandıktan sonra komut ikamesi kullanmayan bir çözüme ihtiyacım olduğuna karar verdim.

Yaklaşımım şimdi stdin içeriğini doğrudan bir değişkene okumak için yerleşik bayrağıread ile birlikte kullanıyor .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"
}

Bu, sondaki satırsonu olan veya olmayan girdileri destekler.

Örnek kullanım:

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

Harika! Sadece merak ediyorum, neden _contents="${_contents}${_line}\n "yeni satırları korumak gibi bir şey kullanmıyorsunuz ?
Eenoku

1
Hakkında mı soruyorsun $'\n'? Bu gerekli, aksi takdirde değişmez kelime \ ve nkarakterler ekliyorsunuz . Kod bloğunuzun sonunda fazladan bir boşluk var, bunun kasıtlı olup olmadığından emin değil, ancak sonraki her satırı fazladan bir boşlukla girintili hale getirir.
dimo414

Açıklama için teşekkürler!
Eenoku

-3

Döngü için bir seferde 1 satıra erişebilirsiniz

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

Tüm içeriği Dosyaya kopyalayın (diyelim line.sh); gerçekleştirmek

chmod +x line.sh
./line.sh

Kişisel fordöngü hattı üzerinden döngü değil, bu kelimeleri üzerinde döngüler. Durumunda, /etc/passwdher satırda sadece bir kelime bulunur. Ancak, diğer dosyalar satır başına birden çok kelime içerebilir.
mpb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.