سؤال

أريد تنفيذ أمر طويل الأمد في Bash، والتقاط حالة الخروج الخاصة به، و قمزة مخرجاتها.

لذلك أفعل هذا:

command | tee out.txt
ST=$?

المشكلة هي أن المتغير ST يلتقط حالة الخروج tee وليس من الأمر.كيف يمكنني حل هذا؟

لاحظ أن الأمر يعمل لفترة طويلة وأن إعادة توجيه الإخراج إلى ملف لمشاهدته لاحقًا ليس حلاً جيدًا بالنسبة لي.

هل كانت مفيدة؟

المحلول

وهناك دعا $PIPESTATUS متغير باش الداخلي. انها مجموعة التي تحمل حالة خروج من كل أمر في حياتك الماضي خط أنابيب المقدمة من الأوامر.

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

وأو بديل آخر والذي يعمل أيضا مع قذائف أخرى (مثل zsh) سيكون لتمكين pipefail:

set -o pipefail
...

والخيار الأول لا <م> لا العمل مع zsh بسبب جملة مختلف قليلا.

نصائح أخرى

وباستخدام set -o pipefail باش هو مفيد

<اقتباس فقرة>   

وpipefail: قيمة الإرجاع من خط أنابيب هو وضع      الأمر الأخير للخروج مع حالة غير صفرية،      أو صفر إذا خرجت أي أمر مع غير الصفر الوضع

وحل البكم: توصيل من خلال توجيه إخراج مسمى (mkfifo). ثم يمكن تشغيل الأمر الثاني.

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?

وهناك مجموعة التي تمنحك حالة خروج من كل أمر في أنبوب.

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1

وهذا الحل يعمل دون استخدام باش ميزات معينة أو الملفات المؤقتة. مكافأة: في النهاية حالة الخروج هو في الواقع حالة خروج وليس بعض سلسلة في ملف

الحالة:

someprog | filter

وتريد وضع الخروج من someprog والإخراج من filter.

وهنا هو بلدي الحل:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

جوابي عن السؤال نفسه على unix.stackexchange.com للحصول على شرح مفصل وبديلا دون subshells وبعض المحاذير.

ومن خلال الجمع بين PIPESTATUS[0] ونتيجة لتنفيذ الأمر exit في المستوى الفرعي، يمكنك الوصول مباشرة إلى قيمة الإرجاع القيادة الأولي الخاص بك:

وcommand | tee ; ( exit ${PIPESTATUS[0]} )

وهنا مثال:

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

وسوف أعطيك:

وreturn value: 1

وهكذا أردت أن أساهم إجابة مثل لlesmana، ولكن أعتقد الألغام وربما القليل من بساطة وقليلا أكثر فائدة محض-بورن قذيفة الحل:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

وأعتقد أن هذا هو أفضل وأوضح من الداخل الى الخارج - command1 سيتم تنفيذ وطباعة انتاجها منتظم على المعياري (ملف واصف 1)، ثم مرة واحدة يتم ذلك، سوف printf تنفيذ ورمز إنهاء icommand1 الطباعة على المعياري لها، ولكن هذا المعياري يتم إعادة توجيه إلى ملف واصف 3.

وأثناء تشغيل Command1 ثم يتم ضخها المعياري لcommand2 (الانتاج printf أبدا يجعل من لcommand2 لأننا إرسالها إلى ملف واصف 3 بدلا من 1، وهو ما يقرأ الأنابيب). ثم نعيد توجيه الانتاج command2 لملف اصف 4، بحيث يبقى أيضا من ملف واصف 1 - لأننا نريد اصف ملف 1 مجانا في وقت لاحق قليلا، لأننا سوف تجلب الانتاج printf على واصف ملف 3 تتراجع إلى اصف ملف 1 - لأن هذا هو ما استبدال الأوامر (وbackticks)، والتقاط وهذا ما سوف تحصل على وضعها في متغير

وبت النهائي من السحر هو أن exec 4>&1 أولا فعلنا كأمر منفصل - أنه يفتح ملف اصف 4 كنسخة المعياري قذيفة الخارجي. استبدال الأمر سيتم التقاط كل ما هو مكتوب على مستوى الخروج من منظور الأوامر داخله - ولكن منذ الناتج command2 وسوف ملف اصف 4 بقدر ما يتعلق الأمر استبدال الأوامر، استبدال الأمر لا استيلاء عليها - ولكن بمجرد أن يحصل "الخروج" من استبدال القيادة هو فعال لا تزال جارية لفي البرنامج النصي اصف الملف الكلي 1.

exec 4>&1 يجب أن يكون أمر منفصل لأن العديد من قذائف المشتركة لا أحب ذلك عند محاولة الكتابة إلى واصف ملف داخل إجراء تبديل الأوامر، التي يتم فتحها في الأمر "الخارجي" الذي يستخدم تبديل. لذلك هذا هو أبسط وسيلة المحمولة للقيام بذلك.)

ويمكنك ننظر في الأمر بطريقة أقل تقنية وأكثر لعوب، كما لو أن مخرجات الأوامر والقفز بعضها البعض: أنابيب Command1 لcommand2، ثم يقفز انتاج printf على مدى القيادة 2 بحيث command2 لا قبض عليه ثم قيادة الناتج 2 ويقفز فوق والخروج من استبدال القيادة فقط الأراضي كما printf في الوقت المناسب للحصول على التقاطها بواسطة استبدال بحيث ينتهي في المتغير، والناتج command2 يذهب في طريقه مرح يتم كتابتها إلى الإخراج القياسي ، تماما كما هو الحال في الأنابيب العادية.

وبالإضافة إلى ذلك، كما أفهمها، $? سوف لا تزال تحتوي على رمز الإرجاع من الأمر الثاني في الأنابيب، لأن المهام المتغيرة، بدائل الأوامر، والأوامر مجمع كلها شفافة بشكل فعال في رمز الإرجاع من الأمر داخلها، لذلك في حالة العودة من command2 يجب ان تحصل على نشر بها - وهذا، وعدم وجود تحديد وظيفة إضافية، هو السبب في أنني أعتقد أن هذا قد يكون حلا أفضل نوعا ما من واحد التي اقترحتها lesmana

.

ولكل المحاذير يذكر lesmana، فمن الممكن أن command1 سوف في مرحلة ما ينتهي باستخدام واصفات ملف 3 أو 4، وذلك لتكون أكثر قوة، يمكن أن تفعله:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

ملحوظة التي تستخدم الأوامر المركب في بلدي على سبيل المثال، ولكن subshells (باستخدام ( ) بدلا من { } ستعمل أيضا، على الرغم من أن ربما تكون أقل كفاءة.)

وأوامر ترث اصفات الملف من العملية التي تطلق عليهم، وبالتالي فإن الخط الثاني بأكمله يرث ملف اصف أربعة، وسوف الأمر مجمع تليها 3>&1 يرث واصف الملفات الثلاثة. لذلك 4>&- يتأكد أن الأمر مركب الداخلي لن يرث ملف اصف أربعة، وسوف 3>&- لا يرث ملف اصف ثلاثة، لذلك command1 يحصل على "نظافة"، بيئة أكثر القياسية. هل يمكن أيضا نقل 4>&- الداخلية بجانب 3>&-، ولكن الرقم الأول لماذا لا مجرد تحد من نطاقها كماأقصى حد ممكن.

ولست متأكدا من عدد المرات التي تستخدم أشياء ملف اصف ثلاثة وأربعة مباشرة - أعتقد أكثر من مرة البرامج تستخدم syscalls التي ترجع واصفات ملف لم تستخدم-في-ولحظة، ولكن أحيانا كود يكتب إلى ملف واصف 3 مباشرة، واعتقد (يمكن أن أتصور برنامج فحص واصف ملف لمعرفة ما إذا كان مفتوحا، واستخدامها إذا كان، أو يتصرف بشكل مختلف وفقا لذلك إذا لم يكن). وبالتالي فإن الأخير هو على الارجح أفضل أن نأخذ في الاعتبار واستخدام للقضايا ذات الغرض العام.

في أوبونتو وديبيان، يمكنك apt-get install moreutils. هذا يحتوي على أداة مساعدة تسمى mispipe أن يعود الوضع للخروج من الأمر الأول في الأنابيب.

وPIPESTATUS [@] يجب أن يتم نسخها إلى مجموعة مباشرة بعد الأمر بإرجاع الأنابيب. أي يقرأ من PIPESTATUS [@] سوف يمحو المحتويات. نسخه إلى مجموعة أخرى إذا كنت تخطط لفحص حالة جميع الأوامر الأنابيب. "$؟" هي القيمة نفس العنصر الأخير من "$ {PIPESTATUS [@]}"، والقراءة على ما يبدو لتدمير "$ {PIPESTATUS [@]}"، لكنني لم يتم التحقق تماما هذا.

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

وهذا لن ينجح إذا كانت الأنابيب في قذيفة الفرعية. للحصول على حل لتلك المشكلة،
يمكنك الاطلاع على باش pipestatus في قيادة backticked؟

(command | tee out.txt; exit ${PIPESTATUS[0]})

وعلى عكس @ الجواب cODAR في هذا بإرجاع رمز إنهاء الأصلي للأمر الأول، وليس فقط 0 من أجل نجاح و 127 للفشل. ولكن كما أشارChaoran من يمكنك الاتصال فقط ${PIPESTATUS[0]}. ومن المهم مع ذلك أن كل ما وضع بين قوسين.

وخارج باش، يمكنك القيام به:

bash -o pipefail  -c "command1 | tee output"

وهذا مفيد على سبيل المثال في البرامج النصية النينجا حيث من المتوقع أن يتم /bin/sh قذيفة.

إن أبسط طريقة للقيام بذلك في باش عادي هي الاستخدام استبدال العملية بدلاً من خط الأنابيب.هناك العديد من الاختلافات، لكنها ربما لا تهم كثيرًا بالنسبة لحالة الاستخدام الخاصة بك:

  • عند تشغيل خط الأنابيب، ينتظر bash حتى تكتمل جميع العمليات.
  • إرسال Ctrl-C إلى bash يجعله يقتل جميع عمليات خط الأنابيب، وليس فقط العملية الرئيسية.
  • ال pipefail الخيار و PIPESTATUS المتغير لا علاقة له باستبدال العملية.
  • ربما أكثر

مع استبدال العملية، يبدأ bash العملية وينسى الأمر، ولا يكون مرئيًا حتى jobs.

الاختلافات المذكورة جانبا ، consumer < <(producer) و producer | consumer متكافئة في الأساس.

إذا كنت تريد قلب العملية "الرئيسية"، فما عليك سوى قلب الأوامر واتجاه الاستبدال إلى producer > >(consumer).في حالتك:

command > >(tee out.txt)

مثال:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

كما قلت، هناك اختلافات عن تعبير الأنابيب.قد لا تتوقف العملية أبدًا عن العمل، إلا إذا كانت حساسة لإغلاق الأنبوب.على وجه الخصوص، قد يستمر في كتابة الأشياء إلى stdout الخاص بك، الأمر الذي قد يكون مربكًا.

والصرفة قذيفة الحل:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

والآن مع cat الثانية حلت محلها false:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

تجدر الإشارة القط الأول فشل أيضا، لأنه المعياري يحصل مغلقة على ذلك. ترتيب الأوامر فشل في السجل هو الصحيح في هذا المثال، ولكن لا تعتمد على ذلك.

وهذا الأسلوب يسمح لالتقاط المعياري وستدير للأوامر فردية حتى تتمكن من ثم تفريغ ذلك أيضا في ملف السجل في حالة حدوث خطأ، أو مجرد حذفه إذا لم يكن الخطأ (مثل إخراج دد).

وعلى قاعدة @ بريان-ق-ويلسون الجواب الصورة. هذه الوظيفة باش المساعد:

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

واستخدامها على النحو التالي:

1: يجب get_bad_things تنجح، ولكن يجب أن تنتج أي الإخراج؛ ولكننا نريد أن نرى الانتاج أنه لا ينتج

get_bad_things | grep '^'
pipeinfo 0 1 || return

2: كل خط الأنابيب يجب أن تنجح

thing | something -q | thingy
pipeinfo || return

قد يكون أحيانا أكثر بساطة وأكثر وضوحا لاستخدام الأوامر الخارجية، بدلا من حفر في تفاصيل باش. خط أنابيب ، من الحد الأدنى من اللغة عملية البرمجة <لأ href = "HTTP: / /www.skarnet.org/software/execline/ "يختلط =" نوفولو noreferrer "> execline أو مخارج مع رمز الإرجاع من الأمر الثاني و*، تماما مثل خط أنابيب sh لا، ولكن على عكس sh، فإنه يسمح عكس اتجاه الأنابيب، حتى نتمكن من التقاط رمز الإرجاع من عملية المنتج (أدناه هو كل على سطر الأوامر sh، ولكن مع execline تثبيت):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

وعن طريق pipeline لديه نفس الاختلافات لخطوط الأنابيب باش الأم كما استبدال عملية سحق المستخدمة في الإجابة # 43972501 .

* في الواقع pipeline لا خروج على الإطلاق ما لم يكن هناك خطأ. ينفذ إلى الأمر الثاني، لذلك هو الأمر الثاني أن يفعل العودة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top