Wiem, że mogę rozwiązać ten problem na kilka sposobów, ale zastanawiam się, czy można to zrobić tylko przy użyciu wbudowanych bashów, a jeśli nie, jaki jest najbardziej efektywny sposób.
Mam plik z zawartością typu
AAA
B C DDD
FOO BAR
przez co rozumiem tylko, że ma kilka linii, a każda linia może zawierać spacje. Chcę uruchomić polecenie takie jak
cmd AAA "B C DDD" "FOO BAR"
Jeśli użyję cmd $(< file)
, dostanę
cmd AAA B C DDD FOO BAR
a jeśli użyję cmd "$(< file)"
, dostanę
cmd "AAA B C DDD FOO BAR"
Jak uzyskać traktowanie każdej linii dokładnie jednego parametru?
bash
command-substitution
Old Pro
źródło
źródło
Odpowiedzi:
Przenośny:
Lub za pomocą podpowłoki, aby
IFS
zmiany opcji i były lokalne:Powłoka wykonuje dzielenie pól i generowanie nazw plików na podstawie podstawienia zmiennej lub polecenia, które nie jest w podwójnych cudzysłowach. Musisz więc wyłączyć generowanie nazw plików
set -f
i skonfigurować podział pól za pomocą,IFS
aby tylko nowe linie były oddzielnymi polami.W konstruktach bash i ksh niewiele można zyskać. Możesz ustawić
IFS
lokalnie dla funkcji, ale nieset -f
.W bash lub ksh93 możesz przechowywać pola w tablicy, jeśli chcesz przekazać je do wielu poleceń. Musisz kontrolować rozszerzanie w momencie budowania tablicy. Następnie
"${a[@]}"
rozwija się do elementów tablicy, po jednym na słowo.źródło
Możesz to zrobić za pomocą tablicy tymczasowej.
Ustawiać:
Wypełnij tablicę:
Użyj tablicy:
Nie można znaleźć sposobu na zrobienie tego bez tej zmiennej tymczasowej - chyba że
IFS
zmiana nie jest ważnacmd
, w takim przypadku:powinien to zrobić.
źródło
IFS
zawsze wprawia mnie w zakłopotanie.IFS=$'\n' cmd $(<input)
nie działaIFS=$'\n'; cmd $(<input); unset IFS
działa. Czemu? Myślę, że użyję(IFS=$'\n'; cmd $(<input))
IFS=$'\n' cmd $(<input)
nie działa, ponieważ ustawia się tylkoIFS
w środowiskucmd
.$(<input)
jest rozwijany, aby utworzyć polecenie przed wykonaniem przypisania doIFS
.Wygląda na to, że kanoniczny sposób na zrobienie tego
bash
jest podobnylub, jeśli twoja wersja bash ma
mapfile
:Jedyną różnicę, którą mogę znaleźć między plikiem mapy a pętlą podczas odczytu w porównaniu z linią jednowierszową
polega na tym, że ten pierwszy przekształci pustą linię w pusty argument, podczas gdy jednowierszowy zignoruje pustą linię. W tym przypadku i tak wolę liniowe zachowanie i tak wolę, więc podwójny bonus za to, że jest kompaktowy.
Chciałbym użyć,
IFS=$'\n' cmd $(<file)
ale to nie działa, ponieważ$(<file)
jest interpretowane jako wiersz poleceń, zanim zacznieIFS=$'\n'
obowiązywać.Chociaż w moim przypadku to nie działa, nauczyłem się, że wiele narzędzi obsługuje linie kończące,
null (\000)
zamiastnewline (\n)
których znacznie ułatwia to, na przykład, nazwy plików, które są częstymi źródłami takich sytuacji :wysyła listę w pełni kwalifikowanych nazw plików jako argumentów do md5 bez globowania, interpolacji lub czegokolwiek. To prowadzi do niewbudowanego rozwiązania
chociaż to również ignoruje puste linie, chociaż przechwytuje linie, które mają tylko białe znaki.
źródło
cmd $(<file)
wartości bez cytowania (używanie zdolności bash do dzielenia słów) jest zawsze ryzykownym zakładem. Jeśli którykolwiek wiersz*
, zostanie rozwinięty przez powłokę do listy plików.Możesz użyć wbudowanego bash,
mapfile
aby odczytać plik do tablicylub niesprawdzony
xargs
może to zrobićźródło
xargs
też nie pomaga.xargs -d
lubxargs -L
-d
opcji ixargs -L 1
uruchamia polecenie raz na linię, ale wciąż dzieli argumenty na białe znaki.mapfile
jest dla mnie bardzo przydatny, ponieważ pobiera puste wiersze jako elementy tablicy, czegoIFS
nie robi metoda.IFS
traktuje ciągłe znaki nowej linii jako pojedynczy separator ... Dzięki za przedstawienie, ponieważ nie byłem świadomy polecenia (chociaż na podstawie danych wejściowych OP i oczekiwanej linii poleceń wydaje się, że naprawdę chce zignorować puste wiersze).Najlepszy sposób, jaki mogłem znaleźć. Po prostu działa.
źródło
IFS
do przestrzeni, ale chodzi o to, aby nie podzielić na przestrzeni?Plik
Najbardziej podstawową pętlą (przenośną) do dzielenia pliku na nowe wiersze jest:
Który wydrukuje:
Tak, z domyślnym IFS = spacetabnewline.
Dlaczego to działa?
IFS
potrzeby zmiany .set -f
potrzebne.-r
(raw) polega na uniknięciu usuwania większości ukośników odwrotnych.To nie zadziała, jeśli konieczne jest dzielenie i / lub globowanie. W takich przypadkach potrzebna jest bardziej złożona struktura.
Jeśli potrzebujesz (nadal przenośny), aby:
IFS= read -r line
IFS=':' read -r a b c
.Podziel plik na inny znak (nieprzenośny, działa z ksh, bash, zsh):
Ekspansja
Oczywiście tytuł twojego pytania dotyczy podziału wykonania polecenia na nowej linii, unikając podziału na spacje.
Jedynym sposobem na uzyskanie podziału od powłoki jest pozostawienie rozszerzenia bez cudzysłowów:
Jest to kontrolowane przez wartość IFS, a przy niekwotowanych rozszerzeniach stosowane jest również globowanie. Aby wykonać tę pracę, potrzebujesz:
Usuń zaznaczenie opcji powłoki globbing
set +f
:set + f IFS = '' cmd $ (<plik)
Oczywiście zmienia to wartość IFS i globowania dla pozostałej części skryptu.
źródło