স্টাডাউট / স্ট্ডারকে ইন্টারলিভিং থেকে বাধা দেয় কী?


14

বলুন আমি কিছু প্রক্রিয়া চালাচ্ছি:

#!/usr/bin/env bash

foo &
bar &
baz &

wait;

আমি উপরের স্ক্রিপ্টটি এভাবে চালিত করি:

foobarbaz | cat

যতদূর আমি বলতে পারি, প্রক্রিয়াগুলির কোনও যখন স্টাডাউট / স্ট্ডারকে লিখেন, তাদের আউটপুট কখনই ইন্টারলেভ করে না - স্ট্ডিওর প্রতিটি লাইন পারমাণবিক বলে মনে হয়। ওটা কিভাবে কাজ করে? প্রতিটি লাইনের পারমাণবিক কীভাবে নিয়ন্ত্রণ করতে পারে?


3
আপনার কমান্ডগুলি কতটা ডেটা আউটপুট দেয়? এগুলি কয়েক কিলোবাইট আউটপুট তৈরি করার চেষ্টা করুন।
কুসালানন্দ

আপনি বোঝাতে চেয়েছেন যে কমান্ডগুলির মধ্যে একটি নতুন লাইনের আগে কয়েক কেবি আউটপুট দেয়?
আলেকজান্ডার মিলস

না, মত এই কিছু: unix.stackexchange.com/a/452762/70524
muru

উত্তর:


23

তারা ইন্টারলিভ করে! আপনি কেবল সংক্ষিপ্ত আউটপুট বিস্ফোরণের চেষ্টা করেছেন, যা আনস্লিট্ট থেকে যায়, তবে বাস্তবে কোনও নির্দিষ্ট আউটপুট আনস্প্লিট থাকার নিশ্চয়তা দেওয়া শক্ত hard

আউটপুট বাফারিং

এটি নির্ভর করে প্রোগ্রামগুলি কীভাবে তাদের আউটপুটকে বাফার করে। Stdio গ্রন্থাগার যে অধিকাংশ প্রোগ্রাম ব্যবহার যখন তারা লেখা ব্যবহারসমূহ বাফার আউটপুট আরো কার্যকর করা। প্রোগ্রাম কোনও ফাইলে লেখার জন্য একটি লাইব্রেরি ফাংশনকে কল করার সাথে সাথে ডেটা আউটপুট দেওয়ার পরিবর্তে ফাংশনটি এই ডেটাটি একটি বাফারে সংরক্ষণ করে এবং বাফারটি পূরণ করার পরে কেবল তথ্যটি আউটপুট করে। এর অর্থ ব্যাচগুলিতে আউটপুট হয়। আরও স্পষ্টভাবে, তিনটি আউটপুট মোড রয়েছে:

  • আনফফার্ড: বাফার ব্যবহার না করেই ডেটা অবিলম্বে লেখা হয়। প্রোগ্রামটি যদি আউটপুটটিকে ছোট ছোট টুকরো করে লেখায় তবে এটি ধীর হতে পারে, উদাহরণস্বরূপ অক্ষর অনুসারে। এটি আদর্শ ত্রুটির জন্য ডিফল্ট মোড।
  • সম্পূর্ণ বাফার: ডেটা কেবল তখনই লেখা হয় যখন বাফারটি পূর্ণ থাকে। স্টাইডার ব্যতীত কোনও পাইপে বা নিয়মিত ফাইলে লেখার সময় এটি ডিফল্ট মোড।
  • লাইন-বাফার করা: প্রতিটি নিউলাইনের পরে ডেটা লেখা হয় বা যখন বাফার পূর্ণ থাকে। স্টারডার ব্যতীত টার্মিনালে লেখার সময় এটি ডিফল্ট মোড।

প্রোগ্রামগুলি প্রতিটি ফাইল আলাদা করে আচরণ করতে পুনরায় প্রোগ্রাম করতে পারে এবং স্পষ্টভাবে বাফারটিকে ফ্লাশ করতে পারে। কোনও প্রোগ্রাম ফাইলটি বন্ধ করে দেয় বা স্বাভাবিকভাবে প্রস্থান করলে বাফারটি স্বয়ংক্রিয়ভাবে ফ্লাশ হয় is

একই পাইপে লিখিত সমস্ত প্রোগ্রাম যদি হয় লাইন-বাফার মোড ব্যবহার করে, বা আনফারড মোড ব্যবহার করে এবং প্রতিটি লাইন একটি আউটপুট ফাংশনে একক কল দিয়ে লিখতে থাকে এবং লাইনগুলি যদি একক অংশে লেখার জন্য যথেষ্ট ছোট হয়, তবে আউটপুটটি সম্পূর্ণ রেখার আন্তঃবিভাজন হবে। তবে যদি কোনও প্রোগ্রাম পুরোপুরি বাফার মোড ব্যবহার করে বা লাইনগুলি দীর্ঘ হয় তবে আপনি মিশ্র লাইন দেখতে পাবেন।

এখানে একটি উদাহরণ যেখানে আমি দুটি প্রোগ্রাম থেকে আউটপুট ইন্টারলিভ করি। আমি লিনাক্সে জিএনইউ কোর্টিল ব্যবহার করেছি; এই ইউটিলিটির বিভিন্ন সংস্করণ আলাদা আচরণ করতে পারে।

  • yes aaaaaaaaলাইন-বাফার মোডের মূলত সমতুল্য যা লিখে চিরকালের জন্য। yesউপযোগ আসলে একটি সময়ে একাধিক লাইন লিখেছেন, কিন্তু প্রতিটি সময় এটি আউটপুট নির্গত আউটপুট লাইনের একটি পূর্ণ সংখ্যা হয়।
  • echo bbbb; done | grep bbbbbসম্পূর্ণ-বাফার মোডে চিরকালের জন্য লেখেন । এটি 8192 এর বাফার আকার ব্যবহার করে এবং প্রতিটি লাইন 5 বাইট দীর্ঘ long 5 যেহেতু 8192 বিভাজন করে না, তাই লেখকের মধ্যে সীমানা সাধারণত একটি লাইনের সীমানায় হয় না।

আসুন তাদের একসাথে পিচ করা যাক।

$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa

আপনি দেখতে পাচ্ছেন, হ্যাঁ কখনও কখনও গ্রেপ বা বিপরীতভাবে বিঘ্নিত হয়। প্রায় 0.001% লাইনই বাধাগ্রস্ত হয়েছিল, তবে এটি ঘটেছিল। আউটপুটটি এলোমেলোভাবে তৈরি হয়েছে যাতে ব্যাঘাতের সংখ্যা পৃথক হয় তবে আমি প্রতিবার অন্তত কয়েকটি বাধা দেখেছি। বাফার প্রতি লাইনের সংখ্যা হ্রাস হওয়ায় বিঘ্নিত হওয়ার সম্ভাবনা বাড়ার পরে লাইনগুলি দীর্ঘ হলে বাধাপ্রাপ্ত লাইনের একটি উচ্চতর ভগ্নাংশ থাকবে।

আউটপুট বাফারিং সামঞ্জস্য করার বিভিন্ন উপায় রয়েছে । প্রধানগুলি হ'ল:

  • stdbuf -o0জিএনইউ কোর্টিলস এবং ফ্রিবিএসডি-তে কিছু অন্যান্য সিস্টেমে পাওয়া প্রোগ্রামের সাথে ডিফল্ট সেটিংস পরিবর্তন না করে স্টেডিও লাইব্রেরি ব্যবহার করে এমন প্রোগ্রামগুলিতে বাফারিং বন্ধ করুন । আপনি বিকল্পভাবে লাইন বাফারিংয়ে স্যুইচ করতে পারেন stdbuf -oL
  • প্রোগ্রামটির আউটপুট কেবলমাত্র এই উদ্দেশ্যে তৈরি করা টার্মিনালের মাধ্যমে নির্দেশিত করে লাইন বাফারিং-এ স্যুইচ করুন unbuffer। কিছু প্রোগ্রাম অন্যান্য উপায়ে অন্যরকম আচরণ করতে পারে, উদাহরণস্বরূপ, grepযদি এর আউটপুটটি টার্মিনাল হয় তবে ডিফল্টরূপে রঙগুলি ব্যবহার করে।
  • প্রোগ্রামটি কনফিগার করুন, উদাহরণস্বরূপ, --line-bufferedজিএনইউ গ্রেপ পাস করে।

আসুন আবার উপরে স্নিপেটটি দেখুন, এবার উভয় পক্ষের লাইন বাফারিংয়ের সাথে।

{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb

সুতরাং এবার হ্যাঁ কখনও গ্রেপ বাধা দেয় না, তবে গ্রেপ কখনও কখনও হ্যাঁতে বাধা দেয়। আমি পরে আসব কেন।

পাইপ ইন্টারলিভিং

যতক্ষণ না প্রতিটি প্রোগ্রাম একবারে একটি লাইন আউটপুট করে এবং লাইনগুলি যথেষ্ট ছোট হয়, আউটপুট লাইনগুলি খুব সুন্দরভাবে পৃথক করা হবে। তবে এটি কাজ করার জন্য লাইনগুলি কত দীর্ঘ হতে পারে তার একটি সীমা রয়েছে। পাইপ নিজেই একটি স্থানান্তর বাফার আছে। যখন কোনও প্রোগ্রাম কোনও পাইপে আউটপুট দেয়, তখন লেখক প্রোগ্রাম থেকে পাইপের ট্রান্সফার বাফারে ডেটা অনুলিপি করা হয় এবং তারপরে পাইপের স্থানান্তর বাফার থেকে পাঠক প্রোগ্রামে স্থানান্তরিত হয়। (কমপক্ষে ধারণাগতভাবে - কার্নেল কখনও কখনও এটি একটি একক অনুলিপিতে অনুকূলিত করতে পারে))

পাইপের ট্রান্সফার বাফারের সাথে ফিট করার চেয়ে কপি করার জন্য যদি আরও বেশি ডেটা থাকে তবে কার্নেলটি একবারে একটি বাফারফুল অনুলিপি করে। যদি একাধিক প্রোগ্রাম একই পাইপে লিখতে থাকে এবং কার্নেল যে প্রথম প্রোগ্রামটি বেছে নিয়েছে তারা একাধিক বাফারফুল লিখতে চায়, তবে গ্যারান্টি নেই যে দ্বিতীয় বার কার্নেল আবার একই প্রোগ্রামটি বেছে নেবে। উদাহরণস্বরূপ, পি যদি বাফার আকার হয়, foo2 * পি বাইট barলিখতে চায় এবং 3 বাইট লিখতে চায়, তবে একটি সম্ভাব্য ইন্টারলিভিং হ'ল পি বাইটস foo, তারপরে 3 বাইট barএবং পি বাইটগুলি থেকে foo

আমার সিস্টেমে উপরের হ্যাঁ + গ্রেপের উদাহরণে ফিরে আসার সাথে সাথে yes aaaaএকসাথে 8192-বাইট বাফারের সাথে যতটা পারা যায় তার জন্য অনেকগুলি লাইন লিখতে দেখা যায়। যেহেতু 5 টি বাইট লেখার জন্য রয়েছে (4 টি মুদ্রণযোগ্য অক্ষর এবং নতুন লাইন), এর অর্থ এটি প্রতিবার 8190 বাইট লিখে দেয়। পাইপ বাফার আকার 4096 বাইট। সুতরাং হ্যাঁ থেকে 4096 বাইট পাওয়া সম্ভব, তারপরে গ্রেপ থেকে কিছু আউটপুট, এবং তারপরে বাকী লেখা হ্যাঁ (8190 - 4096 = 4094 বাইট) থেকে পাওয়া যাবে। 4096 বাইটস 819 লাইন aaaaএবং একাকী জন্য ঘর ছেড়ে দেয় a। অতএব এই একাকীটির সাথে একটি লাইন aতারপরে গ্রেপ থেকে একটি লেখার সাথে একটি লাইন দেয় abbbb

যদি আপনি কী চলছে তার বিশদটি দেখতে চান, তবে getconf PIPE_BUF .আপনার সিস্টেমে আপনাকে পাইপ বাফার আকারটি জানিয়ে দেবে এবং প্রতিটি প্রোগ্রামের মাধ্যমে করা সিস্টেম কলগুলির একটি সম্পূর্ণ তালিকা দেখতে পাবেন

strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba

ক্লিন লাইন ইন্টারলিভিংয়ের গ্যারান্টি কীভাবে দেওয়া যায়

লাইনের দৈর্ঘ্য যদি পাইপ বাফার আকারের চেয়ে ছোট হয় তবে লাইন বাফারিং গ্যারান্টি দেয় যে আউটপুটটিতে কোনও মিশ্র লাইন থাকবে না।

লাইনের দৈর্ঘ্য যদি আরও বড় হতে পারে তবে একাধিক প্রোগ্রাম একই পাইপে লেখার সময় স্বেচ্ছাসেবী মিশ্রণ এড়ানোর কোনও উপায় নেই। বিচ্ছেদ নিশ্চিত করার জন্য, আপনাকে প্রতিটি প্রোগ্রামকে আলাদা পাইপে লিখিত করতে হবে এবং লাইনগুলি একত্রিত করার জন্য একটি প্রোগ্রাম ব্যবহার করতে হবে। উদাহরণস্বরূপ, জিএনইউ সমান্তরাল এটি ডিফল্টরূপে করে।


আকর্ষণীয়, সুতরাং এটি নিশ্চিত করার একটি ভাল উপায় কী হতে পারে যে সমস্ত লাইনগুলি catপরমাণুতে লিখিত হয়েছিল , যেমন বিড়াল প্রক্রিয়াটি ফু / বার / বাজ থেকে পুরো রেখাগুলি গ্রহণ করে তবে এক থেকে অর্ধ লাইন এবং অন্যটির থেকে অর্ধেক রেখা নয় etc. বাশ স্ক্রিপ্টটি দিয়ে আমি কি কিছু করতে পারি?
আলেকজান্ডার মিলস

1
এটি আমার ক্ষেত্রে প্রযোজ্য যেখানে আমার শত শত ফাইল awkছিল এবং একই আইডির জন্য দুটি (বা আরও) লাইন আউটপুট তৈরি করা হয়েছিল find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }' তবে find -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'এটির সাথে প্রতিটি আইডির জন্য সঠিকভাবে একটি লাইন তৈরি হয়েছিল produced
нιηসнιη

কোনও ইন্টারলিভিং প্রতিরোধ করতে, আমি নোড.জেএস এর মতো একটি প্রোগ্রামিং এনভির সাথে এটি করতে পারি, তবে ব্যাশ / শেল দিয়ে কীভাবে এটি করা যায় তা নিশ্চিত নয়।
আলেকজান্ডার মিলস

1
@ জোল এটি পাইপ বাফার পূরণের কারণে। আমি জানতাম আমাকে গল্পের দ্বিতীয় অংশটি লিখতে হবে ... সম্পন্ন হয়েছে।
গিলস

1
@ ওলেজান্ডারডেনম্যান টিএলডিআর যোগ করেছেন: তারা ইন্টারলিভ করে। কারণ জটিল।
গিলস

1

http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P এর দিকে নজর দিয়েছে:

GNU xargs সমান্তরালভাবে একাধিক কাজ চালানো সমর্থন করে। -P n যেখানে n সমান্তরালভাবে চালানোর জন্য কাজের সংখ্যা।

seq 100 | xargs -n1 -P10 echo "$a" | grep 5
seq 100 | xargs -n1 -P10 echo "$a" > myoutput.txt

এটি অনেক পরিস্থিতিতে ভাল কাজ করবে তবে একটি ছদ্মবেশী ত্রুটি রয়েছে: যদি ~ একটিতে ~ 1000 টির বেশি অক্ষর থাকে তবে প্রতিধ্বনিটি পারমাণবিক নাও হতে পারে (এটি একাধিক রাইটিং () কলগুলিতে বিভক্ত হতে পারে) এবং দুটি ঝুঁকির ঝুঁকি রয়েছে মিশ্রিত করা হবে।

$ perl -e 'print "a"x2000, "\n"' > foo
$ strace -e write bash -c 'read -r foo < foo; echo "$foo"' >/dev/null
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 1008) = 1008
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 993) = 993
+++ exited with 0 +++

ইকো বা প্রিন্টফের একাধিক কল থাকলে অবশ্যই একই সমস্যাটি দেখা দেয়:

slowprint() {
  printf 'Start-%s ' "$1"
  sleep "$1"
  printf '%s-End\n' "$1"
}
export -f slowprint
seq 10 | xargs -n1 -I {} -P4 bash -c "slowprint {}"
# Compare to no parallelization
seq 10 | xargs -n1 -I {} bash -c "slowprint {}"
# Be sure to see the warnings in the next Pitfall!

সমান্তরাল কাজগুলি থেকে প্রাপ্ত ফলাফলগুলি একসাথে মিশ্রিত হয়, কারণ প্রতিটি কাজের ক্ষেত্রে দুটি (বা আরও) পৃথক লিখন () কল থাকে।

আপনার যদি আউটপুটগুলি অমীমাংসিত প্রয়োজন হয়, সুতরাং এটির জন্য এমন সরঞ্জাম ব্যবহার করার পরামর্শ দেওয়া হয় যা আউটপুটটিকে গ্যারান্টি দেয় (যেমন GNU সমান্তরাল)।


বিভাগটি ভুল। xargs echoইকো ব্যাশ বিল্টিনকে কল করে না, তবে echoইউটিলিটি থেকে $PATH। এবং যাইহোক আমি বাশ 4.4 এর সাথে সেই ব্যাশের প্রতিধ্বনি আচরণ পুনরুত্পাদন করতে পারি না। লিনাক্স-এ, 4K এর চেয়ে বড় পাইপকে (/ dev / নাল নয়) লিখতে পারমাণবিক হওয়ার নিশ্চয়তা দেওয়া হয় না।
স্টাফেন চেজেলাস
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.