Komut değiştirme: newline'da bölme ancak boşluk bırakma


30

Bu sorunu birkaç yolla çözebileceğimi biliyorum, ancak bunu yalnızca bash yerleşiklerini kullanarak yapmanın bir yolu olup olmadığını merak ediyorum ve değilse, bunu yapmanın en etkili yolu nedir.

İçeriği olan bir dosyam var

AAA
B C DDD
FOO BAR

bununla birlikte sadece birkaç satır olduğunu ve her satırın boşluk bırakıp bırakmayacağını kastediyorum. Gibi bir komut çalıştırmak istiyorum

cmd AAA "B C DDD" "FOO BAR"

Kullanırsam cmd $(< file)alırım

cmd AAA B C DDD FOO BAR

ve ben kullanırsanız cmd "$(< file)"alıyorum

cmd "AAA B C DDD FOO BAR"

Her çizgiye tam olarak bir parametreyi nasıl ele alabilirim?


Yanıtlar:


26

portably:

set -f              # turn off globbing
IFS='
'                   # split at newlines only
cmd $(cat <file)
unset IFS
set +f

Veya IFSve seçeneğinin yerel olarak değişmesi için alt kabuk kullanmak :

( set -f; IFS='
'; exec cmd $(cat <file) )

Kabuk, çift tırnak içine alınmayan değişken veya komut değişiminin sonucu olarak alan bölme ve dosya adı oluşturma işlemini gerçekleştirir. Bu nedenle set -f, IFSyalnızca yeni satırları ayrı alanlar yapmak için dosya adı oluşturmayı kapatmanız ve alan bölmeyi yapılandırmanız gerekir.

Bash veya ksh yapılarıyla kazanılacak fazla bir şey yok. IFSBir işleve yerel yapabilirsiniz , ancak değil set -f.

Bash veya ksh93'te, birden çok komuta geçirmeniz gerekirse, alanları bir dizide saklayabilirsiniz. Diziyi oluştururken genişletmeyi kontrol etmeniz gerekir. Sonra "${a[@]}"her kelime için bir tane olmak üzere dizinin elemanlarına genişler.

set -f; IFS=$'\n'
a=($(cat <file))
set +f; unset IFS
cmd "${a[@]}"

10

Bunu geçici bir dizi ile yapabilirsiniz.

Kurmak:

$ cat input
AAA
A B C
DE F
$ cat t.sh
#! /bin/bash
echo "$1"
echo "$2"
echo "$3"

Diziyi doldur:

$ IFS=$'\n'; set -f; foo=($(<input))

Diziyi kullanın:

$ for a in "${foo[@]}" ; do echo "--" "$a" "--" ; done
-- AAA --
-- A B C --
-- DE F --

$ ./t.sh "${foo[@]}"
AAA
A B C
DE F

Bu geçici değişken olmadan bunu yapmanın bir yolunu bulamıyorum - IFSdeğişiklik için önemli olmadığı sürece, cmdbu durumda:

$ IFS=$'\n'; set -f; cmd $(<input) 

yapmalı.


IFSher zaman kafamı karıştırıyor. IFS=$'\n' cmd $(<input)çalışmıyor IFS=$'\n'; cmd $(<input); unset IFSçalışır. Niye ya? Sanırım kullanacağım(IFS=$'\n'; cmd $(<input))
Old Pro

6
@OldPro IFS=$'\n' cmd $(<input)çalışmıyor çünkü yalnızca IFSortamına ayarlar cmd. $(<input)Atama IFSgerçekleştirilmeden önce komutu oluşturmak üzere genişletilir .
Gilles 'SO- kötülük' dur

8

Bunu yapmanın kurallı yolu bashgibi bir şey gibi görünüyor

unset args
while IFS= read -r line; do 
    args+=("$line") 
done < file

cmd "${args[@]}"

veya, bash sürümünüz varsa mapfile:

mapfile -t args < filename
cmd "${args[@]}"

Mapfile ile while-döngüsü arasında bulabildiğim tek fark, bir linere karşı

(set -f; IFS=$'\n'; cmd $(<file))

birincisi boş bir satırı boş bir argümana dönüştürür, birincisi boş bir satırı görmezden gelir. Bu durumda tek-liner davranışı yine de tercih ettiğim şeydir, bu yüzden bunun üzerinde iki katı bonus olması gerekir.

Kullanırdım IFS=$'\n' cmd $(<file)ama işe yaramadı, çünkü yürürlüğe $(<file)girmeden önce komut satırını oluşturmak için yorumlandı IFS=$'\n'.

Benim durumumda işe yaramasa da, artık bir çok aracın sonlandırma çizgilerini desteklediğini, null (\000)bunun yerine newline (\n), bu durumların ortak kaynakları olan dosya isimleriyle uğraşırken bunu daha kolay hale getirdiğini öğrendim. :

find / -name '*.config' -print0 | xargs -0 md5

herhangi bir globbing veya enterpolasyon yapmadan veya herhangi bir şey yapmadan md5'e argüman olarak tam nitelikli dosya adlarının bir listesini besler. Bu yerleşik olmayan çözüme yol açar

tr "\n" "\000" <file | xargs -0 cmd

bununla birlikte, boş satırları yok sayar, ancak yalnızca boşluk içeren satırları yakalar.


Kullanılması cmd $(<file)alıntı (ayrık kelimeleri bash yeteneğini kullanarak) olmadan değerleri her zaman riskli bir bahis. Herhangi bir satır varsa *, kabuk tarafından bir dosya listesine genişletilecektir.

3

Bash yerleşik mapfiledosyasını dosyayı bir diziye okumak için kullanabilirsiniz

mapfile -t foo < filename
cmd "${foo[@]}"

veya denenmemiş xargsolabilir

xargs cmd < filename

Map dosyası dokümantasyonundan: "map dosyası ortak ya da taşınabilir bir kabuk özelliği değildir". Gerçekten de sistemimde desteklenmiyor. xargsya da yardımcı olmuyor.
Eski Pro

xargs -dYa ihtiyacınız olacakxargs -L
James Youngman

@James, hayır, bir -dseçeneğim yok ve xargs -L 1komutu satır başına bir kez çalıştırıyor, ancak hala boşlukları da artar.
Eski Pro

1
@OldPro, "ortak veya taşınabilir bir kabuk özelliği" yerine "bunu yalnızca bash yerleşiklerini kullanarak yapmanın bir yolunu" istemiştiniz. Bash sürümünüz çok eskiyse güncelleyebilir misiniz?
glenn jackman

mapfileBenim için çok kullanışlıdır, çünkü IFSmetodun yapamadığı dizi maddeleri olarak boş satırlar kapar . IFSbitişik yeni satırları tek bir sınırlayıcı olarak algılar ... Komutun farkında olmadığım için sunduğunuz için teşekkürler (OP'nin giriş verilerine ve beklenen komut satırına dayanarak aslında boş satırları yoksaymak istiyor gibi görünüyor).
Peter.O

0
old=$IFS
IFS='  #newline
'
array=`cat Submissions` #input the text in this variable
for ...  #use parts of variable in the for loop
... 
done
IFS=$old

Bulabildiğim en iyi yol. Sadece işe yarıyor.


Ve neden ayarlarsanız çalışır IFSboşluğa, ama soru etmektir değil uzay bölmek?
RalfFriedl

0

Dosya

Bir dosyayı yeni satırlarda bölmek için kullanılan en basit döngü (taşınabilir):

#!/bin/sh
while read -r line; do            # get one line (\n) at a time.
    set -- "$@" "$line"           # store in the list of positional arguments.
done <infile                      # read from a file called infile.
printf '<%s>' "$@" ; echo         # print the results.

Hangi yazdıracak:

$ ./script
<AAA><A B C><DE F>

Evet, varsayılan IFS ile = spacetabnewline.

Neden çalışıyor

  • IFS, kabuk tarafından girişi birkaç değişkene bölmek için kullanılacaktır . Yalnızca bir değişken olduğundan, kabuk tarafından hiçbir bölme gerçekleştirilmez. Yani, hiçbir değişiklik IFSgerekmedi.
  • Evet, önde gelen ve sondaki boşluklar / sekmeler kaldırılıyor, ancak bu durumda sorun gibi görünmüyor.
  • Hayır, genleşme belirtilmemiş olduğundan , hiçbir küreselleşme yapılmaz . Yani, gerek yok .set -f
  • Kullanılan tek dizi (veya gerekli) dizi benzeri konumsal parametrelerdir.
  • -r(Ham) seçenek en Tersbölünün kaldırılmasını kaçınmaktır.

Yani olacak değil bölme ve / veya globbing gerekiyorsa çalışır. Bu gibi durumlarda daha karmaşık bir yapıya ihtiyaç vardır.

Şunlara ihtiyacınız varsa (hala taşınabilir):

  • Baştaki ve sondaki boşlukların / sekmelerin kaldırılmasından kaçının, aşağıdakileri kullanın: IFS= read -r line
  • Bazı karakter, kullanımı üzerine vars Split satır: IFS=':' read -r a b c.

Dosyayı başka bir karaktere bölün (taşınabilir değil, ksh, bash, zsh ile çalışır):

IFS=':' read -d '+' -r a b c

Genişleme

Tabii ki, sorunuzun başlığı, boşluklardaki bölünmeyi önleyen yeni satırlara bir komut yürütme yapmaktır.

Kabuktan ayrılmanın tek yolu tırnak işareti olmadan bir genişleme bırakmaktır:

echo $(< file)

Bu, IFS'nin değeri ile kontrol edilir ve fiyatlandırılmamış genişlemelerde, küreselleşme de uygulanır. Bu işi halletmek için ihtiyacın var:

  • IFS'yi sadece yeni satıra bölmek için yalnızca yeni satıra ayarlayın .
  • Küresel kabuk seçeneğini serbest bırakın set +f:

    + f IFS = '' cmd $ 'ı ayarla (<dosya)

Tabii ki, bu IFS ve senaryo geri kalanı için globbing değerini değiştirir.

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.