कमांड प्रतिस्थापन: न्यूलाइन पर विभाजन लेकिन जगह नहीं


30

मुझे पता है कि मैं इस समस्या को कई तरीकों से हल कर सकता हूं, लेकिन मैं सोच रहा हूं कि क्या केवल बैश-इन का उपयोग करके ऐसा करने का कोई तरीका है, और यदि नहीं, तो इसे करने का सबसे कुशल तरीका क्या है।

मेरे पास सामग्री जैसी फाइल है

AAA
B C DDD
FOO BAR

जिसके द्वारा मेरा मतलब है कि इसकी कई रेखाएँ हैं और प्रत्येक पंक्ति में स्थान हो सकता है या नहीं भी हो सकता है। मैं जैसे कमांड चलाना चाहता हूं

cmd AAA "B C DDD" "FOO BAR"

अगर मैं उपयोग करता cmd $(< file)हूं तो मुझे मिलता है

cmd AAA B C DDD FOO BAR

और अगर मैं उपयोग करता cmd "$(< file)"हूं तो मुझे मिलता है

cmd "AAA B C DDD FOO BAR"

मैं प्रत्येक पंक्ति को एक बिल्कुल एक पैरामीटर का इलाज कैसे करवा सकता हूं?


जवाबों:


26

portably:

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

या IFSस्थानीय और परिवर्तन को बदलने के लिए एक उपधारा का उपयोग करना :

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

शेल एक चर या कमांड प्रतिस्थापन के परिणाम पर फ़ील्ड विभाजन और फ़ाइल नाम का कार्य करता है जो दोहरे उद्धरण में नहीं है। इसलिए आपको फ़ाइल नाम वाली पीढ़ी को बंद करना होगा set -f, और IFSकेवल नए क्षेत्रों को अलग फ़ील्ड बनाने के लिए फ़ील्ड विभाजन को कॉन्फ़िगर करना होगा ।

बैश या ksh निर्माण के साथ प्राप्त करने के लिए बहुत कुछ नहीं है। आप IFSकिसी फ़ंक्शन के लिए स्थानीय बना सकते हैं , लेकिन नहीं set -f

Bash या ksh93 में, आप फ़ील्ड को एक सरणी में स्टोर कर सकते हैं, यदि आपको उन्हें कई कमांड में पास करने की आवश्यकता है। सरणी बनाते समय आपको विस्तार पर नियंत्रण करना होगा। फिर "${a[@]}"एक शब्द के अनुसार, सरणी के तत्वों तक फैलता है।

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

10

आप इसे एक अस्थायी सरणी के साथ कर सकते हैं।

सेट अप:

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

सरणी भरें:

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

सरणी का उपयोग करें:

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

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

उस अस्थायी चर के बिना ऐसा करने का एक तरीका नहीं खोज सकते - जब तक कि IFSबदलाव cmdकिस स्थिति में महत्वपूर्ण न हो :

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

करना चाहिए।


IFSहमेशा मुझे भ्रमित करता है। IFS=$'\n' cmd $(<input)काम नहीं करता है। IFS=$'\n'; cmd $(<input); unset IFSकाम करेगा। क्यूं कर? मुझे लगता है कि मैं का उपयोग करता हूँ(IFS=$'\n'; cmd $(<input))
पुराने प्रो

6
@OldPro IFS=$'\n' cmd $(<input)काम नहीं करता क्योंकि यह केवल IFSके वातावरण में सेट होता है cmd$(<input)कार्य करने IFSसे पहले कमांड बनाने के लिए विस्तारित किया जाता है।
गिल्स एसओ- बुराई को रोकें '28

8

ऐसा करने के लिए विहित तरीका bashकुछ इस तरह दिखता है

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

cmd "${args[@]}"

या, यदि आपके बैश के संस्करण में है mapfile:

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

एकमात्र अंतर जो मैं मैपफाइल और एक-लाइनर के बीच में पढ़े गए लूप के बीच पा सकता हूं

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

यह है कि पूर्व एक खाली लाइन को एक खाली तर्क में बदल देगा, जबकि एक-लाइनर एक रिक्त रेखा को अनदेखा करेगा। इस मामले में एक-लाइनर व्यवहार वह है जो मैं वैसे भी पसंद करूंगा, इसलिए उस पर डबल बोनस कॉम्पैक्ट होना चाहिए।

मैं उपयोग करूंगा IFS=$'\n' cmd $(<file)लेकिन यह काम नहीं करता है, क्योंकि प्रभावी $(<file)होने से पहले कमांड लाइन बनाने के लिए व्याख्या की IFS=$'\n'जाती है।

हालांकि यह मेरे मामले में काम नहीं करता है, मैंने अब यह जान लिया है कि बहुत से उपकरण null (\000)इसके बजाय लाइनों को समाप्त करने का समर्थन newline (\n)करते हैं, जिसके साथ काम करते समय बहुत आसान हो जाता है, कहते हैं, फ़ाइल नाम, जो इन स्थितियों के सामान्य स्रोत हैं। :

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

किसी भी ग्लोबिंग या इंटरपोलिंग या जो भी बिना md5 के तर्क के रूप में पूरी तरह से योग्य फ़ाइल नामों की एक सूची खिलाती है। यह गैर-निर्मित समाधान की ओर जाता है

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

हालाँकि, यह भी, खाली लाइनों की उपेक्षा करता है, हालांकि यह उन लाइनों को कैप्चर करता है जिनमें केवल व्हाट्सएप होता है।


cmd $(<file)उद्धरण के बिना मूल्यों का उपयोग करना (शब्दों को विभाजित करने के लिए बैश की क्षमता का उपयोग करना) हमेशा एक जोखिम भरा दांव होता है। यदि कोई रेखा है, तो *इसे शेल द्वारा फाइलों की सूची में विस्तारित किया जाएगा।

3

आप mapfileफ़ाइल को किसी सरणी में पढ़ने के लिए बने बैश का उपयोग कर सकते हैं

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

या, अप्रशिक्षित, xargsयह कर सकता है

xargs cmd < filename

मैपफाइल प्रलेखन से: "मैपफाइल एक आम या पोर्टेबल शेल सुविधा नहीं है"। और वास्तव में यह मेरे सिस्टम पर समर्थित नहीं है। xargsमदद भी नहीं करता है।
पुराने प्रो

आपको आवश्यकता होगी xargs -dयाxargs -L
जेम्स यंगमैन

@ जेम्स, नहीं, मेरे पास कोई -dविकल्प नहीं है और xargs -L 1प्रति पंक्ति एक बार कमांड चलाता है लेकिन फिर भी व्हाट्सएप पर विभाजन होता है।
पुराने प्रो

1
@ ओल्डप्रो, अच्छी तरह से आपने "सामान्य या पोर्टेबल शेल सुविधा" के बजाय "केवल बैश बिल्ट-इन का उपयोग करके ऐसा करने का तरीका" पूछा। यदि आपका बैश का संस्करण बहुत पुराना है, तो क्या आप इसे अपडेट कर सकते हैं?
ग्लेन जैकमैन

mapfileमेरे लिए बहुत उपयोगी है, क्योंकि यह खाली लाइनों को सरणी आइटम के रूप में पकड़ लेता है, जो IFSविधि ऐसा नहीं करती है। IFSएक ही सीमांकक के रूप में सन्निहित नई कहानियों को मानता है ... इसे प्रस्तुत करने के लिए धन्यवाद, जैसा कि मुझे कमांड के बारे में पता नहीं था (हालांकि, ओपी के इनपुट डेटा और अपेक्षित कमांड लाइन के आधार पर, ऐसा लगता है कि वह वास्तव में रिक्त लाइनों को अनदेखा करना चाहता है)।
पीटर

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

सबसे अच्छा तरीका मुझे मिल सकता है। बस काम करता है।


और अगर आप IFSअंतरिक्ष में जाते हैं तो यह काम क्यों करता है , लेकिन सवाल यह है कि यह अंतरिक्ष में विभाजित नहीं है?
राल्फफ्राइडल

0

फ़ाइल

सबसे मूल लूप (पोर्टेबल) एक फाइल को नए सिरे से विभाजित करने के लिए है:

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

जो प्रिंट करेगा:

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

हां, डिफ़ॉल्ट IFS = के साथ spacetabnewline

यह काम क्यों करता है

  • IFS का उपयोग शेल द्वारा इनपुट को कई चर में विभाजित करने के लिए किया जाएगा । चूंकि केवल एक चर है, शेल द्वारा कोई विभाजन नहीं किया जाता है। तो, कोई बदलाव की IFSजरूरत नहीं है।
  • हां, अग्रणी और अनुगामी रिक्त स्थान / टैब निकाले जा रहे हैं, लेकिन यह इस मामले में समस्या नहीं लगती है।
  • नहीं, नहीं ग्लोबिंग के रूप में कोई विस्तार है किया जाता है गैर उद्धृत । तो, कोई set -fजरूरत नहीं है।
  • केवल उपयोग की गई सरणी (या आवश्यक) सरणी की तरह स्थितीय पैरामीटर है।
  • -r(कच्चे) विकल्प सबसे बैकस्लैश को हटाने से बचना है।

यदि विभाजन और / या ग्लोबिंग की आवश्यकता होती है तो यह काम नहीं करेगा । ऐसे मामलों में एक अधिक जटिल संरचना की आवश्यकता होती है।

यदि आपको जरूरत है (अभी भी पोर्टेबल):

  • अग्रणी और अनुगामी रिक्त स्थान / टैब को हटाने से बचें, उपयोग करें: IFS= read -r line
  • स्प्लिट लाइन कुछ वर्णों पर var के लिए, उपयोग करें IFS=':' read -r a b c:।

फ़ाइल को किसी अन्य वर्ण पर विभाजित करें (पोर्टेबल नहीं, ksh, bash, zsh के साथ काम करता है):

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

विस्तार

बेशक, आपके सवाल का शीर्षक रिक्त स्थान पर विभाजन से बचने के लिए नए सिरे पर एक कमांड निष्पादन को विभाजित करने के बारे में है।

शेल से विभाजन प्राप्त करने का एकमात्र तरीका उद्धरण के बिना विस्तार छोड़ना है:

echo $(< file)

इसे IFS के मूल्य द्वारा नियंत्रित किया जाता है, और, निर्विवाद विस्तार पर, ग्लोबिंग भी लागू किया जाता है। उस काम को करने के लिए, आपको आवश्यकता है:

  • नई लाइन के लिए सेट आईएफएस केवल , केवल नई पंक्ति पर बंटवारे मिलता है।
  • ग्लोबिंग शेल विकल्प को खोलें set +f:

    सेट + एफ IFS = '' cmd $ (<फ़ाइल)

बेशक, यह बाकी स्क्रिप्ट के लिए IFS और ग्लोबिंग के मूल्य को बदल देता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.