เรียกใช้สองคำสั่งในอาร์กิวเมนต์เดียว (โดยไม่มีสคริปต์)


28

ฉันจะดำเนินการสองคำสั่งในอินพุตเดียวโดยไม่ต้องพิมพ์อินพุตนั้นสองครั้งได้อย่างไร

ตัวอย่างเช่นคำสั่ง stat บอกจำนวนมากเกี่ยวกับไฟล์ แต่ไม่ได้ระบุประเภทไฟล์:

stat fileName

คำสั่ง file บอกประเภทของไฟล์:

file fileName

คุณสามารถทำได้ในหนึ่งบรรทัดด้วยวิธีนี้:

stat fileName ; file fileName

อย่างไรก็ตามคุณต้องพิมพ์ชื่อไฟล์สองครั้ง

คุณจะรันคำสั่งทั้งสองในอินพุตเดียวกันได้อย่างไร (โดยไม่ต้องพิมพ์อินพุตหรือตัวแปรของอินพุตสองครั้ง)

ใน Linux ฉันรู้วิธีไพพ์เอาต์พุต แต่คุณไพพ์อินพุตได้อย่างไร?


14
เพื่อให้ชัดเจนสิ่งเหล่านั้นไม่ใช่ "อินพุต" แต่เป็นอาร์กิวเมนต์ (พารามิเตอร์ aka) อินพุตสามารถไพพ์ผ่าน<(อินพุตจากไฟล์ไปทางด้านซ้าย) หรือ|(อินพุตจากสตรีมไปทางด้านขวา) มีความแตกต่าง
goldilocks

6
ไม่ได้คำตอบสำหรับคำถามของคุณ แต่บางทีคำตอบสำหรับปัญหาของคุณ: ในทุบตีที่yank-last-argคำสั่ง (เริ่มต้นลัดMeta-.หรือMeta-_; Metaมักจะเป็นซ้ายAltกุญแจ) จะคัดลอกพารามิเตอร์ที่ผ่านมาจากบรรทัดคำสั่งก่อนหน้าเข้าไปในหนึ่งในปัจจุบัน ดังนั้นหลังจากดำเนินการstat fileNameคุณพิมพ์file [Meta-.]และไม่ต้องพิมพ์fileNameอีกครั้ง ฉันมักจะใช้มัน
Dubu

2
และความแตกต่างก็คือว่า<และ>มีการเปลี่ยนเส้นทางไม่ท่อ ไปป์ไลน์เชื่อมต่อสองกระบวนการในขณะที่การเปลี่ยนเส้นทางเพียงกำหนด stdin / stdout ใหม่
bsd

@bdowning: ใช่ แต่คุณเปลี่ยนเส้นทางไปป์ แต่ไม่ใช่คุณ คุณสามารถเปลี่ยนเส้นทางการเริ่มต้นไปป์ที่จะอ่านจากไฟล์บนดิสก์หรือคุณสามารถเปลี่ยนเส้นทางจุดสิ้นสุดของไพพ์ไลน์เพื่อเขียนไปยังไฟล์บนดิสก์ ยังคงเป็นท่อ ;-)
James Haigh

คำตอบ:


21

นี่เป็นอีกวิธีหนึ่ง:

$ stat filename && file "$_"

ตัวอย่าง:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

ที่ทำงานในและbash zshที่ยังใช้งานได้mkshและdashเฉพาะเมื่อมีการโต้ตอบ ใน AT&T ksh นั้นจะทำงานเฉพาะเมื่อfile "$_"อยู่ในบรรทัดที่แตกต่างจากที่statหนึ่ง


1
!$หนึ่งจะไม่ทำงานเพราะ!$ขยายไปยังคำพูดสุดท้ายของเหตุการณ์ประวัติศาสตร์ที่ผ่านมา (ดังนั้นบรรทัดคำสั่งที่ป้อนก่อนหน้านี้ไม่ได้มาจากคำสั่งสถิติ)
Stéphane Chazelas

@ StéphaneChazelas: โอ้ใช่ฉันทำผิดฉันลบมัน
cuonglm

32

อาจจะได้รับการเคาะนิ้วของฉันสำหรับเรื่องนี้นี่คือการรวมกันของการขยายตัวทุบตี bash brace และ eval ที่ดูเหมือนว่าจะทำเคล็ดลับ

eval {stat,file}" fileName;"


2
การขยายตัวรั้งที่เกิดขึ้นในไม่csh คัดลอกจาก รหัสนั้นจะใช้ได้กับ csh, tcsh, ksh, zsh, ปลาและ bash ทั้งหมด bashbashksh
Stéphane Chazelas

1
น่าเสียดายที่คุณสูญเสียความสามารถในการ "ลบแท็บ" (ป้อนอัตโนมัติ) ชื่อไฟล์เนื่องจากการอ้างอิง
Lonniebiz

ขออภัย แต่ฉันไม่สามารถต้านทานได้เช่นกัน
59

ขออภัยที่นี่มีปัญหาevalอะไร (อ้างถึงความคิดเห็นยอดนิยม)
user13107

28

ด้วยzshคุณสามารถใช้ฟังก์ชั่นที่ไม่ระบุชื่อ:

(){stat $1; file $1} filename

ด้วยeslambdas:

@{stat $1; file $1} filename

คุณสามารถทำได้:

{ stat -; file -;} < filename

(การดำเนินการstatครั้งแรกตามที่fileจะอัปเดตเวลาเข้าถึง)

ฉันต้องการ:

f=filename; stat "$f"; file "$f"

แม้ว่านั่นคือสิ่งที่ตัวแปรสำหรับ


3
{ stat -; file -;} < filenameเป็นเวทมนตร์ statต้องตรวจสอบเพื่อดูว่า stdin นั้นเชื่อมต่อกับไฟล์และรายงานคุณสมบัติของไฟล์หรือไม่ สันนิษฐานว่าไม่file
เข้าหา

1
@ 1_CR ใช่ stat -บอกstatให้ทำfstat()ใน fd 0
Stéphane Chazelas

4
{ $COMMAND_A -; $COMMAND_B; } < filenameตัวแปรจะไม่ทำงานในกรณีทั่วไปถ้า $ COMMAND_A จริงอ่านป้อนข้อมูลใด ๆ ; เช่น{ cat -; cat -; } < filenameจะพิมพ์เนื้อหาของชื่อไฟล์เพียงครั้งเดียว
Max Nanasy

2
stat -ได้รับการจัดการเป็นพิเศษตั้งแต่ coreutils-8.0 (ต.ค. 2009) ในรุ่นก่อนหน้าคุณจะได้รับข้อผิดพลาด (เว้นแต่คุณจะมีไฟล์ที่เรียกว่า-จากการทดสอบก่อนหน้านี้ซึ่งในกรณีนี้คุณจะได้รับผลลัพธ์ที่ผิด ... )
mr .spuratic

1
@ mr.spuratic ใช่และ BSDs, IRIX และ zsh stats จะไม่จดจำเช่นกัน ด้วย zsh และ IRIX stat คุณสามารถใช้stat -f0แทน
Stéphane Chazelas

9

คำตอบทั้งหมดเหล่านี้ดูเหมือนจะเป็นสคริปต์ให้ฉันมากขึ้นหรือน้อยลง สำหรับวิธีแก้ปัญหาที่ไม่เกี่ยวข้องกับการเขียนสคริปต์จริงๆและสมมติว่าคุณนั่งอยู่ที่แป้นพิมพ์ลงในเชลล์คุณสามารถลอง:

สถิติ somefile Enter
ไฟล์Esc_   Enter

ใน bash, zsh และ ksh อย่างน้อยในโหมด vi และ emacs ให้กด Escape แล้วขีดเส้นใต้แทรกคำสุดท้ายจากบรรทัดก่อนหน้าไปยังบัฟเฟอร์การแก้ไขสำหรับบรรทัดปัจจุบัน

หรือคุณสามารถใช้การทดแทนประวัติ:

สถิติEnter
ไฟล์somefile ! $Enter

ใน bash, zsh, csh, tcsh และเชลล์อื่น ๆ ส่วนใหญ่!$จะถูกแทนที่ด้วยคำสุดท้ายของบรรทัดคำสั่งก่อนหน้าเมื่อบรรทัดคำสั่งปัจจุบันถูกแยกวิเคราะห์


ตัวเลือกอื่น ๆ ที่มีอยู่ใน zsh / bash ที่เป็นEsc _อย่างไร คุณช่วยลิงค์กับเอกสารทางการได้ไหม
Abhijeet Rastogi


1
zsh: linux.die.net/man/1/zshzleและค้นหาคำว่า "insert-last-word"
godlygeek

1
@shadyabhi ^ ลิงก์สำหรับทั้งการกดคีย์มาตรฐานของ bash และ zsh โปรดทราบว่า bash กำลังใช้ readline ดังนั้น (ส่วนใหญ่) bash นั้นจะทำงานในแอปพลิเคชันใด ๆ ที่ใช้ไลบรารี readline
godlygeek

3

วิธีที่ฉันได้พบว่าทำสิ่งนี้อย่างสมเหตุสมผลก็คือการใช้ xargs ใช้ไฟล์ / ไพพ์และแปลงเนื้อหาเป็นอาร์กิวเมนต์ของโปรแกรม สามารถรวมกับทีที่แยกสตรีมและส่งไปยังสองโปรแกรมขึ้นไปในกรณีที่คุณต้องการ:

echo filename | tee >(xargs stat) >(xargs file) | cat

ต่างจากคำตอบอื่น ๆ มากมายซึ่งจะทำงานใน bash และ shell อื่น ๆ ส่วนใหญ่ภายใต้ Linux ฉันจะแนะนำว่านี่เป็นกรณีการใช้งานที่ดีของตัวแปร แต่ถ้าคุณไม่สามารถใช้มันได้นี่เป็นวิธีที่ดีที่สุดในการทำกับท่อและยูทิลิตี้อย่างง่าย (สมมติว่าคุณไม่มีเชลล์แฟนซี)

นอกจากนี้คุณสามารถทำสิ่งนี้สำหรับโปรแกรมจำนวนเท่าใดก็ได้โดยเพิ่มในลักษณะเดียวกับสองโปรแกรมนี้หลังจากทีออฟ

แก้ไข

ตามที่แนะนำในความคิดเห็นมีข้อบกพร่องสองสามข้อในคำตอบนี้ข้อแรกคือศักยภาพสำหรับการพัวพันของเอาต์พุต สิ่งนี้สามารถแก้ไขได้ดังนี้:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

สิ่งนี้จะบังคับให้คำสั่งรันเป็นลำดับและเอาต์พุตจะไม่ถูกเชื่อมโยงกัน

ปัญหาที่สองคือการหลีกเลี่ยงการทดแทนกระบวนการซึ่งไม่มีในเชลล์บางตัวซึ่งสามารถทำได้โดยใช้ tpipe แทนที่จะเป็นทีดังนั้น:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

นี้ควรพกพาไปได้เกือบทุกเชลล์ (ฉันหวังว่า) และแก้ปัญหาในการยกย่องอื่น ๆ แต่ฉันเขียนอันสุดท้ายจากหน่วยความจำเนื่องจากระบบปัจจุบันของฉันไม่มี tpipe ให้ใช้งาน


1
กระบวนการทดแทนเป็นคุณลักษณะ ksh ที่ทำงานเฉพาะในksh(ไม่โดเมนสาธารณะพันธุ์) และbash zshโปรดทราบว่าstatและfileจะทำงานพร้อมกันดังนั้นเอาต์พุตของพวกเขาอาจถูกพันกัน (ฉันสมมติว่าคุณกำลังใช้| catเพื่อบังคับบัฟเฟอร์เอาต์พุตเพื่อ จำกัด ผลกระทบนั้นหรือไม่)
Stéphane Chazelas

@ StéphaneChazelasคุณถูกต้องเกี่ยวกับแมวเมื่อใช้มันฉันไม่ได้จัดการที่จะผลิตกรณีของการป้อนข้อมูลแบบอินเทอร์เลซเมื่อใช้มัน อย่างไรก็ตามฉันจะลองทำบางสิ่งเพื่อสร้างโซลูชันแบบพกพาให้มากขึ้นในไม่ช้า
Vality

3

คำตอบนี้คล้ายกับคำตอบอื่น ๆ :

stat filename   && ไฟล์! #: 1

ต่างจากคำตอบอื่น ๆ ซึ่งทำซ้ำอาร์กิวเมนต์สุดท้ายโดยอัตโนมัติจากคำสั่งก่อนหน้าคำตอบนี้ให้คุณนับ:

ls -ld ชื่อไฟล์   && ไฟล์! #: 2

ไม่เหมือนกับคำตอบอื่น ๆ ซึ่งไม่จำเป็นต้องมีเครื่องหมายคำพูด:

stat "useless cat"  &&  file !#:1

หรือ

echo Sometimes a '$cigar' is just a !#:3.

2

นี่คล้ายกับคำตอบอื่น ๆ สองสามข้อ แต่สามารถพกพาได้มากกว่าคำตอบนี้ และง่ายกว่าคำตอบนี้ :

sh -c 'สถิติ "$ 0"; ไฟล์ "$ 0" ' ชื่อไฟล์

หรือ

sh -c 'สถิติ "$ 1"; ไฟล์ "$ 1"' ดวลจุดโทษชื่อไฟล์

ซึ่งได้รับมอบหมายให้sh $0แม้ว่าจะมีการพิมพ์มากกว่าสามตัวอักษรรูปแบบ (ที่สอง) นี้จะดีกว่าเนื่องจาก$0เป็นชื่อที่คุณให้กับสคริปต์แบบอินไลน์ โดยจะใช้ตัวอย่างเช่นในข้อความผิดพลาดโดยเปลือกสำหรับสคริปต์ว่าเหมือนเมื่อfileหรือstatไม่สามารถพบได้หรือไม่สามารถดำเนินการด้วยเหตุผลใดก็ตาม


ถ้าคุณต้องการที่จะทำ

สถิติชื่อไฟล์1 ชื่อไฟล์2 ชื่อไฟล์3 ... ; ไฟล์ชื่อไฟล์1 ชื่อไฟล์2 ชื่อไฟล์3 ...

สำหรับไฟล์ตามอำเภอใจให้เลือก

sh -c 'สถิติ "$ @"; ไฟล์ "$ @" 'sh filename 1 ชื่อไฟล์2 ชื่อไฟล์3 ...

ซึ่งผลงานเพราะจะเทียบเท่ากับ"$@""$1" "$2" "$3" …


นั่น$0คือชื่อที่คุณต้องการให้กับสคริปต์แบบอินไลน์ มันใช้เป็นตัวอย่างในข้อความแสดงข้อผิดพลาดโดยเชลล์สำหรับสคริปต์นั้น ชอบเมื่อfileหรือstatไม่สามารถพบได้หรือไม่สามารถดำเนินการด้วยเหตุผลใด ๆ ดังนั้นคุณต้องการบางสิ่งที่มีความหมายที่นี่ shถ้าไม่ได้รับแรงบันดาลใจเพียงแค่ใช้
Stéphane Chazelas

1

ฉันคิดว่าคุณกำลังถามคำถามที่ผิดอย่างน้อยก็ในสิ่งที่คุณพยายามจะทำ statและfileคำสั่งกำลังรับพารามิเตอร์ซึ่งเป็นชื่อของไฟล์และไม่ใช่เนื้อหาของมัน มันเป็นคำสั่งในภายหลังซึ่งจะอ่านไฟล์ที่ระบุโดยชื่อที่คุณระบุ

ไปป์ควรจะมีสองปลายหนึ่งใช้เป็นอินพุทและอีกอันหนึ่งเป็นเอาท์พุทและนี่เป็นเหตุผลที่คุณอาจต้องทำการประมวลผลที่แตกต่างกันไปพร้อมกับเครื่องมือต่าง ๆ จนกว่าคุณจะได้ผลลัพธ์ที่ต้องการ การบอกว่าคุณรู้วิธีการส่งออกไพพ์ แต่ไม่ทราบวิธีการอินพุตไพพ์ไม่ถูกต้องในหลักการ

ในกรณีของคุณคุณไม่จำเป็นต้องไปป์ไลน์เลยเนื่องจากคุณต้องใส่ข้อมูลในรูปแบบของชื่อไฟล์ และแม้ว่าเครื่องมือเหล่านั้น ( statและfile) จะอ่าน stdin ท่อจะไม่เกี่ยวข้องที่นี่อีกครั้งเนื่องจากอินพุตไม่ควรมีการเปลี่ยนแปลงโดยเครื่องมือใด ๆ เมื่อเข้าสู่เครื่องมือที่สอง


1

สิ่งนี้ควรใช้ได้กับชื่อไฟล์ทั้งหมดในไดเรกทอรีปัจจุบัน

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *

1
สมมติว่าชื่อไฟล์ไม่ได้มีราคาคู่, ทับขวา, ดอลลาร์ backtick, }... -ตัวอักษรและไม่ได้เริ่มต้นด้วย บางprintfการใช้งาน (zsh, ทุบตี ksh93) %qมี ด้วยzshอาจเป็น:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas

@StephaneChezales - ทั้งหมดได้รับการแก้ไขแล้วยกเว้นความเป็นไปได้ของ hardquote ฉันสามารถจัดการกับสิ่งเดียวที่sed s///ฉันคาดเดา แต่มันเกี่ยวกับขอบเขต - และหากผู้คนกำลังใส่ชื่อไฟล์ไว้ในนั้นพวกเขาควรใช้ windows
mikeserv

@ StéphaneChazelas - ไม่เป็นไร - ฉันลืมไปว่ามันง่ายแค่ไหนที่จะจัดการ
mikeserv

การพูดอย่างเคร่งครัดนี่ไม่ใช่คำตอบของคำถามตามที่ถาม:“ คุณจะรันคำสั่งทั้งสองในอินพุตเดียวกันได้อย่างไร ( โดยไม่ต้องพิมพ์อินพุต…สองครั้ง )” (เน้นที่เพิ่ม) คำตอบของคุณนำไปใช้กับไฟล์ทั้งหมดในไดเรกทอรีปัจจุบัน - และจะมี*ครั้งที่สอง stat -- *; file -- *คุณเช่นกันอาจจะบอกว่า
สกอตต์

@Scott - อินพุตไม่ถูกพิมพ์สองครั้ง - *ถูกขยาย การขยายตัวของ*บวกทั้งสองคำสั่งสำหรับทุกอาร์กิวเมนต์ใน *คืออินพุต ดู? printf commands\ %s\;shift *
mikeserv

1

มีการอ้างอิงที่แตกต่างกันสองสามอย่างเกี่ยวกับ 'อินพุต' ที่นี่ดังนั้นฉันจะให้บางสถานการณ์ด้วยความเข้าใจในใจก่อน สำหรับคำตอบที่รวดเร็วของคำถามในรูปแบบที่สั้นที่สุด :

stat testfile < <($1)> outputfile

ข้างต้นจะดำเนินการสถิติใน testfile ใช้ (เปลี่ยนเส้นทาง) มันเป็น STDOUT และรวมไว้ในฟังก์ชั่นพิเศษถัดไป (ส่วน <()) จากนั้นส่งออกผลลัพธ์สุดท้ายของสิ่งที่เคยเป็นไฟล์ใหม่ (outputfile) ไฟล์ถูกเรียกใช้จากนั้นอ้างอิงด้วย bash ในตัว ($ 1 ทุกครั้งหลังจากนั้นจนกว่าคุณจะเริ่มชุดคำสั่งใหม่)

คำถามของคุณดีมากและมีหลายคำตอบและวิธีการทำเช่นนี้ แต่มันจะเปลี่ยนแปลงกับสิ่งที่คุณทำโดยเฉพาะ

ตัวอย่างเช่นคุณสามารถวนซ้ำได้เช่นกันซึ่งค่อนข้างมีประโยชน์ การใช้งานทั่วไปของสิ่งนี้คือในความคิดของรหัส psuedo คือ:

run program < <($output_from_program)> my_own.log

การรับสิ่งนั้นเข้ามาและขยายความรู้นั้นทำให้คุณสามารถสร้างสิ่งต่าง ๆ เช่น:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

สิ่งนี้จะดำเนินการอย่างง่าย ls-a ในไดเรกทอรีปัจจุบันของคุณจากนั้นบอกขณะที่วนลูปผ่านแต่ละผลลัพธ์จาก ls -A ถึง (และนี่คือที่ ๆ มันยุ่งยาก!) grep "thatword" ในผลลัพธ์แต่ละรายการและดำเนินการก่อนหน้านี้เท่านั้น printf (สีแดง) หากพบไฟล์ที่มี "thatword" อยู่จริง นอกจากนี้ยังจะบันทึกผลลัพธ์ของ grep ลงในไฟล์ข้อความใหม่ files_that_matched_thatword

ตัวอย่างผลลัพธ์:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

ทั้งหมดนั้นเพียงพิมพ์ ls -A ผลลัพธ์ไม่มีอะไรพิเศษ เพิ่มบางสิ่งเพื่อให้ grep ในครั้งนี้:

echo "thatword" >> newfile

ตอนนี้เรียกใช้อีกครั้ง:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

ในขณะที่อาจเป็นคำตอบที่เหนื่อยล้ามากกว่าที่คุณกำลังมองหาในปัจจุบัน แต่ฉันเชื่อว่าการเก็บบันทึกย่อที่มีประโยชน์แบบนี้จะช่วยให้คุณได้รับประโยชน์มากขึ้นในความพยายามในอนาคต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.