Jak radzić sobie z surowymi danymi binarnymi w potoku bash?

15

Mam funkcję bash, która przyjmuje plik jako parametr, sprawdza, czy plik istnieje, a następnie zapisuje w pliku wszystko, co wychodzi ze standardowego wejścia. Naiwne rozwiązanie działa dobrze dla tekstu, ale mam problemy z dowolnymi danymi binarnymi.

echo -n '' >| "$file" #Truncate the file
while read lines
do  # Is there a better way to do this? I would like one...
    echo $lines >> "$file"
done
David Souther
źródło

Odpowiedzi:

15

Twój sposób polega na dodawaniu $IFSpodziałów wierszy do wszystkich rzeczy, które zapisuje w przestrzeni dowolnego separatora ( ) używanego do podziału odczytu. Zamiast rozbijać go na nowe linie, po prostu weź całość i przekaż ją dalej. Możesz zmniejszyć cały powyższy kod do tego:

 cat - > $file

Nie potrzebujesz bitu obcięcia, to obetnie i zapisze do niego cały strumień STDIN.

Edytować: Jeśli używasz zsh, możesz po prostu użyć > $filezamiast kota. Przekierowujesz do pliku i obcinasz go, ale jeśli coś tam czeka i czeka na akceptację STDIN, zostanie on odczytany w tym momencie. Myślę, że możesz zrobić coś takiego za pomocą bash, ale musiałbyś ustawić jakiś tryb specjalny.

Caleb
źródło
Nie mogłem uruchomić przykładu przekierowania standardowego, ale zmieniłem przykład kota na> | (Mam zestaw noclobber) działa jak urok. Dzięki za zrobienie mojego dnia ^. ^
David Souther,
+1 dla wersji bez kota. Zawsze unikaj niepotrzebnych kotów;)
rozcietrzewiacz
@rozcietrzewiacz: To prawda, tyle że to była refleksja i się myliłem. To może nie być bezużyteczne użycie kota. Jedyne, co możesz zrobić, to > $file. Działa to tylko jako pierwsza rzecz, która szuka stdin w skrypcie powłoki nadrzędnej. Zasadniczo cały kod Davida można sprowadzić do jednego znaku, ale myślę, że cat -jest bardziej elegancki i mniej kłopotliwy, ponieważ jest zrozumiały na pierwszy rzut oka.
Caleb
Czasami catłączę ze sobą cztery lub pięć s, aby wkurzyć fanatyków UUOC
Michael Mrozek
@MichaelMrozek: Czasami nazywam swoje pliki danych cattylko dlatego, że ludzie, którzy nalegają na ich użycie, muszą koniecznie ćwiczyć gimnastykę, aby przeczytać kod. Nazwane potoki są również dobrym celem.
Caleb,
7

Aby odczytać plik tekstowy dosłownie, nie używaj zwykłego read, który przetwarza dane wyjściowe na dwa sposoby:

  • readinterpretuje \jako znak ucieczki; użyj, read -raby to wyłączyć.
  • readdzieli się na słowa na znaki w $IFS; ustaw IFSna pusty ciąg, aby to wyłączyć.

Zwykle idiomem przetwarzającym plik tekstowy wiersz po wierszu jest

while IFS= read -r line; do 

Aby uzyskać wyjaśnienie tego idiomu, zobacz Dlaczego jest while IFS= readużywany tak często zamiast IFS=; while read..? .

Aby napisać ciąg dosłownie, nie używaj zwykłego echo, który przetwarza ciąg na dwa sposoby:

  • W niektórych powłokach echoprzetwarza odwrotne ukośniki. (W przypadku bash zależy to od ustawienia xpg_echoopcji).
  • Kilka ciągów traktowanych jest jako opcje, np. -nLub -e(dokładny zestaw zależy od powłoki).

Przenośnym sposobem drukowania łańcucha jest dosłownie printf. (Nie ma lepszego sposobu na bash, chyba że wiesz, że twoje wejście nie wygląda jak opcja echo.) Użyj pierwszego formularza, aby wydrukować dokładny ciąg, a drugiego formularza, jeśli chcesz dodać nowy wiersz.

printf %s "$line"
printf '%s\n' "$line"

Jest to odpowiednie tylko do przetwarzania tekstu , ponieważ:

  • Większość pocisków będzie się dusić na znakach zerowych na wejściu.
  • Kiedy czytasz ostatni wiersz, nie możesz wiedzieć, czy na końcu był nowy wiersz, czy nie. (Niektóre starsze powłoki mogą mieć większe problemy, jeśli dane wejściowe nie kończą się nową linią).

Nie można przetwarzać danych binarnych w powłoce, ale współczesne wersje narzędzi na większości unikatów radzą sobie z dowolnymi danymi. Aby przekazać wszystkie dane wejściowe do wyniku, użyj cat. Posiadanie stycznej echo -n ''jest skomplikowanym i nieprzenośnym sposobem na nic nie robienie; echo -nbyłby równie dobry (lub nie w zależności od powłoki) i :jest prostszy i w pełni przenośny.

: >| "$file"
cat >>"$file"

lub prościej

cat >|"$file"

W skrypcie zwykle nie trzeba go używać, >|ponieważ noclobberdomyślnie jest wyłączony.

Gilles „SO- przestań być zły”
źródło
dzięki za wskazanie xpg_echo, to jest problem, który miałem gdzie indziej w moim kodzie i nawet nie zdawałem sobie z tego sprawy. Re noclobber, mam w zwyczaju włączać to w moim bashrc.
David Souther,
0

To zrobi dokładnie to, co chcesz:

( while read -r -d '' ; do
    printf %s'\0' "${REPLY}" ;
  done ;

  # When read hits EOF, it returns non-zero which exits the while loop.
  # That data still needs to be output:
  printf %s "${REPLY}"
) >> ${file}

Zwróć jednak uwagę na użycie pamięci. Odczytuje to dane wejściowe w sposób rozdzielany zerami.

Jeśli na wejściu nie ma bajtów \0 zerowych, bash najpierw będzie musiał odczytać całą zawartość danych wejściowych do pamięci, a następnie wyprowadzić ją.

Jeśli chodzi o krok obcinania:

echo -n '' >| "$file" #Truncate the file

znacznie prostszym i równoważnym jest:

> ${file}   #Truncate the file
Marc Tamsky
źródło