BASH: ใช้ล่าม awk การเลือกแถวจากช่วงที่กำหนด นิพจน์และบล็อก

การแนะนำภาษาที่ยอดเยี่ยมพร้อมชื่อแปลก ๆ

Daniel Robbins ประธาน/ซีอีโอ บริษัท Gentoo Technologies, Inc.

คำอธิบาย: Awk เป็นภาษาที่ยอดเยี่ยมและมีชื่อที่แปลกมาก ในบทความแรกของซีรีส์สามตอนนี้ Daniel Robbins ให้ไว้ แนะนำสั้น ๆพื้นฐานการเขียนโปรแกรมใน awk บทความในอนาคตในชุดนี้จะครอบคลุมหัวข้อขั้นสูงเพิ่มเติม ซึ่งลงท้ายด้วยแอปพลิเคชันสาธิต awk ที่แข็งแกร่งในโลกแห่งความเป็นจริง

แท็กบทความนี้: awk

แจ้งเรื่องนี้!

วันที่: 29/01/2552

ระดับความยาก: ง่าย

ความคิดเห็น: 0 (ดู | เพิ่มความคิดเห็น - เข้าสู่ระบบ)

ให้คะแนนบทความนี้

ในการป้องกัน awk

ในบทความชุดนี้ ฉันจะทำให้ผู้อ่านเป็นโปรแกรมเมอร์ awk ที่มีทักษะ ฉันยอมรับว่า awk ไม่มีชื่อที่ดีที่สุดหรือทันสมัยที่สุด และ awk เวอร์ชัน GNU ที่เรียกว่า gawk ฟังดูแปลกมาก โปรแกรมเมอร์ที่ไม่คุ้นเคยกับภาษานี้เมื่อได้ยินชื่ออาจจินตนาการถึงโค้ดที่เก่าแก่และล้าสมัยที่ผิดพลาดซึ่งสามารถขับเคลื่อนแม้กระทั่งคนที่บ้าคลั่งที่สุดได้ ผู้เชี่ยวชาญที่มีความรู้บน UNIX (ทำให้เขาอุทานว่า "kill -9!" และวิ่งไปดื่มกาแฟตลอดเวลา)

ใช่ awk ไม่ได้ ชื่อที่ยอดเยี่ยม- แต่เป็นภาษาที่ยอดเยี่ยม Awk ได้รับการออกแบบมาเพื่อการประมวลผลข้อความและการรายงาน แต่ก็มีฟีเจอร์ที่ได้รับการพัฒนามาอย่างดีมากมายที่ช่วยให้สามารถเขียนโปรแกรมอย่างจริงจังได้ อย่างไรก็ตาม ไวยากรณ์ของ awk ไม่เหมือนกับภาษาอื่นๆ ตรงที่ไวยากรณ์ของ awk นั้นคุ้นเคยและยืมส่วนที่ดีที่สุดจากภาษาต่างๆ เช่น C, python และ bash (แม้ว่า awk อย่างเป็นทางการจะถูกสร้างขึ้นก่อน python และ bash) Awk เป็นหนึ่งในภาษาเหล่านั้นที่เมื่อได้เรียนรู้แล้วจะกลายเป็นส่วนสำคัญของคลังแสงเชิงกลยุทธ์ของโปรแกรมเมอร์

ก้าวแรกใน awk

มาเริ่มต้นและทดลองใช้ awk เพื่อดูว่ามันทำงานอย่างไร ที่บรรทัดรับคำสั่ง ให้ป้อนคำสั่งต่อไปนี้: $ awk "( print )" /etc/passwd

ผลลัพธ์ควรแสดงเนื้อหาของไฟล์ /etc/passwd ตอนนี้ - คำอธิบายเกี่ยวกับสิ่งที่ awk ทำ เมื่อเรียก awk เราได้ระบุ /etc/passwd เป็นไฟล์อินพุต เมื่อเรารัน awk มันจะประมวลผลคำสั่ง print สำหรับแต่ละบรรทัดใน /etc/passwd ตามลำดับ ผลลัพธ์ทั้งหมดถูกส่งไปยัง stdout และเราได้ผลลัพธ์ที่เหมือนกับผลลัพธ์ คำสั่งแมว/etc/passwd. ตอนนี้เรามาอธิบายบล็อค ( print ) กันดีกว่า ใน awk วงเล็บปีกกาใช้เพื่อจัดกลุ่มกลุ่มข้อความ เช่นเดียวกับใน C กลุ่มข้อความของเรามีคำสั่งพิมพ์เพียงคำสั่งเดียว ใน awk คำสั่ง print จะไม่มี พารามิเตอร์เพิ่มเติมพิมพ์เนื้อหาทั้งหมด เส้นปัจจุบัน.

นี่เป็นอีกตัวอย่างหนึ่งของโปรแกรม awk ที่ทำสิ่งเดียวกัน: $ awk "( print $0 )" /etc/passwd

ใน awk ตัวแปร $0 แสดงถึงบรรทัดปัจจุบันทั้งหมด ดังนั้น print และ print $0 จะทำสิ่งเดียวกันทุกประการ หากต้องการ คุณสามารถสร้างโปรแกรมใน awk ที่จะส่งออกข้อมูลที่ไม่เกี่ยวข้องกับข้อมูลอินพุตโดยสิ้นเชิง ตัวอย่าง: $ awk "( print "" )" /etc/passwd

เมื่อคุณส่งสตริง "" ไปยังคำสั่ง print คำสั่งจะพิมพ์สตริงว่างเสมอ หากคุณทดสอบสคริปต์นี้ คุณจะพบว่า awk ส่งออกบรรทัดว่างหนึ่งบรรทัดสำหรับทุกบรรทัดใน /etc/passwd สิ่งนี้เกิดขึ้นอีกครั้งเนื่องจาก awk รันสคริปต์สำหรับแต่ละบรรทัดในไฟล์อินพุต นี่เป็นอีกตัวอย่างหนึ่ง: $ awk "( print "hiya" )" /etc/passwd

หากคุณเรียกใช้สคริปต์นี้ มันจะเต็มหน้าจอด้วยคำว่า "เย้" -

หลายช่อง

Awk เหมาะอย่างยิ่งสำหรับการประมวลผลข้อความที่แบ่งออกเป็นช่องลอจิคัลหลายช่อง และทำให้ง่ายต่อการเข้าถึงแต่ละช่องจากภายในสคริปต์ awk สคริปต์ต่อไปนี้จะพิมพ์รายการบัญชีทั้งหมดในระบบ: $ /etc/passwd

ในการเรียก awk ในตัวอย่างด้านบน ตัวเลือก –F ระบุ uma เป็นตัวคั่นฟิลด์ เมื่อประมวลผลคำสั่ง print $1 awk จะพิมพ์ฟิลด์แรกที่พบในแต่ละบรรทัดของไฟล์อินพุต นี่เป็นอีกตัวอย่างหนึ่ง: $ awk -F: "( print $1 $3 )" /etc/passwd

นี่คือส่วนย่อยจากเอาต์พุตหน้าจอของสคริปต์นี้:halt7

อย่างที่คุณเห็น awk ส่งออกฟิลด์แรกและฟิลด์ที่สามของไฟล์ /etc/passwd ซึ่งเป็นฟิลด์ชื่อผู้ใช้และ uid ตามลำดับ ในเวลาเดียวกันแม้ว่าสคริปต์จะใช้งานได้ แต่ก็ไม่สมบูรณ์แบบ - ไม่มีช่องว่างระหว่างสองฟิลด์เอาต์พุต! ผู้ที่เคยชินกับการเขียนโปรแกรมด้วย bash หรือ python อาจคาดหวังว่าคำสั่ง print $1 $3 จะแทรกช่องว่างระหว่างสองฟิลด์นี้ อย่างไรก็ตาม เมื่อบรรทัดสองบรรทัดปรากฏติดกันในโปรแกรม awk awk จะเชื่อมบรรทัดทั้งสองเข้าด้วยกันโดยไม่ต้องเว้นวรรคระหว่างบรรทัดเหล่านั้น คำสั่งต่อไปนี้จะแทรกช่องว่างระหว่างฟิลด์: $ awk -F: "( print $1 " " $3 )" /etc/passwd

เมื่อการพิมพ์ถูกเรียกด้วยวิธีนี้ มันจะเชื่อม $1, " " และ $3 เข้าด้วยกันเป็นอนุกรม ทำให้เกิดเอาต์พุตที่มนุษย์สามารถอ่านได้บนหน้าจอ แน่นอน เราสามารถแทรกป้ายกำกับฟิลด์ได้หากจำเป็น: $ awk -F/// "( print "username: " $1 "\t\tuid: " $3" )" /etc/passwd

เป็นผลให้เราได้รับผลลัพธ์ต่อไปนี้: ชื่อผู้ใช้: หยุด uid:7

ชื่อผู้ใช้: โอเปอเรเตอร์ uid:11

ชื่อผู้ใช้: รูท uid:0

ชื่อผู้ใช้: ปิดเครื่อง uid:6

ชื่อผู้ใช้: ซิงค์ uid:5

ชื่อผู้ใช้: bin uid:1

สคริปต์ภายนอก

การส่งผ่านสคริปต์เพื่อ awk เป็นอาร์กิวเมนต์ บรรทัดคำสั่งอาจสะดวกสำหรับข้อความบรรทัดเดียวขนาดเล็ก แต่เมื่อเป็นโปรแกรมหลายบรรทัดที่ซับซ้อน การเขียนสคริปต์จะดีกว่าแน่นอน ไฟล์ภายนอก- จากนั้นคุณสามารถชี้ awk ไปที่ไฟล์สคริปต์นี้ได้โดยใช้ตัวเลือก -f:$ awk -f myscript.awk myfile.in

การวางสคริปต์ในไฟล์ข้อความแยกกันยังช่วยให้คุณได้รับประโยชน์อีกด้วย สิทธิประโยชน์เพิ่มเติมโอเค ตัวอย่างเช่น สคริปต์หลายบรรทัดต่อไปนี้ทำงานเหมือนกับสคริปต์บรรทัดเดียวก่อนหน้านี้ - พิมพ์ฟิลด์แรกของแต่ละบรรทัดจาก /etc/passwd: BEGIN (

ความแตกต่างระหว่างสองวิธีนี้คือวิธีที่เราระบุตัวคั่นฟิลด์ ในสคริปต์นี้ ตัวคั่นฟิลด์จะถูกระบุภายในโดยตัวโปรแกรมเอง (โดยการตั้งค่าตัวแปร FS) ในขณะที่ในตัวอย่างก่อนหน้านี้ FS ได้รับการกำหนดค่าโดยการส่งตัวเลือก awk -F" บนบรรทัดคำสั่ง โดยปกติวิธีที่ดีที่สุดคือระบุตัวคั่นฟิลด์ภายในสคริปต์ เพียงเพราะว่าคุณไม่จำเป็นต้องจำอาร์กิวเมนต์บรรทัดคำสั่งอื่น เราจะดูตัวแปร FS โดยละเอียดเพิ่มเติมในบทความนี้

บล็อก BEGIN และ END

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

Awk ยังมีบล็อกพิเศษอีกบล็อกที่เรียกว่าบล็อก END Awk ดำเนินการบล็อกนี้หลังจากประมวลผลทุกบรรทัดในไฟล์อินพุตแล้ว โดยทั่วไป บล็อก END จะใช้เพื่อทำการคำนวณขั้นสุดท้ายหรือผลลัพธ์เอาต์พุตที่ควรปรากฏที่ส่วนท้ายของสตรีมเอาต์พุต

นิพจน์ทั่วไปและบล็อก

Awk ให้คุณใช้งานได้ การแสดงออกปกติสำหรับการดำเนินการแบบเลือกของแต่ละบล็อกโปรแกรม ขึ้นอยู่กับว่านิพจน์ทั่วไปตรงกับบรรทัดปัจจุบันหรือไม่ นี่คือตัวอย่างสคริปต์ที่พิมพ์เฉพาะบรรทัดที่มีลำดับอักขระ foo:/foo/ ( print )

แน่นอน คุณสามารถใช้นิพจน์ทั่วไปที่ซับซ้อนกว่านี้ได้ นี่คือสคริปต์ที่จะพิมพ์เฉพาะบรรทัดที่มีทศนิยม: /+\.*/ ( print )

นิพจน์และบล็อก

มีหลายวิธีในการเลือกดำเนินการบล็อกของโปรแกรม เราสามารถวางนิพจน์บูลีนใดๆ ไว้หน้าบล็อกโปรแกรมเพื่อควบคุมการทำงานของบล็อกนั้น Awk จะดำเนินการบล็อกโปรแกรมก็ต่อเมื่อนิพจน์บูลีนก่อนหน้าประเมินเป็นจริงเท่านั้น สคริปต์ตัวอย่างต่อไปนี้จะแสดงฟิลด์ที่สามของทุกบรรทัดที่ฟิลด์แรกถูก fred หากฟิลด์แรกของบรรทัดปัจจุบันไม่ใช่ fred awk จะดำเนินการประมวลผลไฟล์ต่อและจะไม่ออกคำสั่งการพิมพ์สำหรับบรรทัดปัจจุบัน: :$1 == "fred" ( print $3 )

ข้อเสนอของ AWK ชุดเต็มตัวดำเนินการเปรียบเทียบ รวมถึง "==", " ตามปกติ<", ">", "<=", ">=" และ "!=" นอกจากนี้ awk ยังมีตัวดำเนินการ "~" และ "!~" ซึ่งหมายถึง "ตรงกัน" และ "ไม่ตรงกัน" โดยจะวางตัวแปรไว้ทางด้านซ้ายของตัวดำเนินการและนิพจน์ทั่วไป ทางด้านขวา นี่คือตัวอย่างที่พิมพ์เฉพาะฟิลด์ที่สามของบรรทัดหากฟิลด์ที่ห้าของบรรทัดเดียวกันมีลำดับอักขระ root:$5 ~ /root/ ( print $3 )

คำสั่งแบบมีเงื่อนไข

Awk ยังมีคำสั่ง if คล้าย C ที่ดีมากอีกด้วย หากต้องการ คุณสามารถเขียนสคริปต์ก่อนหน้าใหม่ได้โดยใช้ if:(

ถ้า ($5 ~ /root/) (

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

นี่คือเพิ่มเติม ตัวอย่างที่ซับซ้อนถ้าคำสั่งใน awk อย่างที่คุณเห็น แม้ว่าจะมีเงื่อนไขที่ซับซ้อน แต่คำสั่ง if ก็ดูเหมือนกันกับคำสั่ง C:(

ถ้า ($1 == "foo") (

ถ้า ($2 == "foo") (

) อย่างอื่นถ้า ($1 == "บาร์") (

การใช้คำสั่ง if เราสามารถเปลี่ยนโค้ดนี้ได้: ! /matchme/ ( พิมพ์ $1 $3 $4 )

แบบนี้: (

ถ้า ($0 !~ /matchme/) (

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

Awk ยังช่วยให้คุณสามารถใช้ตัวดำเนินการบูลีน "||" ("ตรรกะ OR") และ "&&" ("ตรรกะ AND") ซึ่งช่วยให้คุณสามารถสร้างนิพจน์บูลีนที่ซับซ้อนมากขึ้น: ($1 == "foo") && ($2 == "bar") ( print )

ตัวอย่างนี้จะส่งออกเฉพาะแถวที่ฟิลด์แรกเป็น foo และฟิลด์ที่สองคือ bar

ตัวแปรตัวเลข!

จนถึงตอนนี้เราได้พิมพ์ตัวแปรสตริง สตริงทั้งหมด หรือฟิลด์เฉพาะแล้ว อย่างไรก็ตาม awk ยังช่วยให้เราสามารถเปรียบเทียบทั้งจำนวนเต็มและจำนวนจุดลอยตัวได้ การใช้นิพจน์ทางคณิตศาสตร์ทำให้การเขียนสคริปต์นับตัวเลขเป็นเรื่องง่ายมาก เส้นว่างในไฟล์. นี่คือหนึ่งในสคริปต์ดังกล่าว: BEGIN ( x=0 )

END ( พิมพ์ "พบ " x " บรรทัดว่าง :)"

ในบล็อก BEGIN เราเริ่มต้นตัวแปรจำนวนเต็ม x เป็นศูนย์ จากนั้น แต่ละครั้งที่ awk พบบรรทัดว่าง จะดำเนินการคำสั่ง x=x+1 โดยเพิ่มขึ้น x 1 เมื่อบรรทัดทั้งหมดได้รับการประมวลผลแล้ว END block จะถูกดำเนินการ และ awk จะพิมพ์ผลรวมสุดท้าย โดยระบุ จำนวนบรรทัดว่างที่พบ

ตัวแปรสตริง

ข้อดีอย่างหนึ่งเกี่ยวกับตัวแปร awk ก็คือตัวแปรเหล่านี้เป็น "ธรรมดาและตัวพิมพ์เล็ก" ฉันเรียกตัวแปร awk ว่า "string" เนื่องจากตัวแปร awk ทั้งหมดถูกจัดเก็บไว้ภายในเป็นสตริง ในเวลาเดียวกัน ตัวแปร awk เป็นแบบ "ธรรมดา" เนื่องจากคุณสามารถดำเนินการทางคณิตศาสตร์กับตัวแปรได้ และตัวแปรนั้นมีตัวแปรที่ถูกต้องหรือไม่ เส้นจำนวน, awk จะดูแลการแปลงสตริงเป็นตัวเลขโดยอัตโนมัติ หากต้องการดูว่าฉันหมายถึงอะไร โปรดดูตัวอย่างนี้: x="1.01"

# เราสร้าง x มี *string* "1.01"

# เราเพิ่งเพิ่ม 1 ใน *string*

# นี่คือความคิดเห็นนะครับ :)

Awk จะส่งออก: 2.01

อยากรู้! แม้ว่าเราจะกำหนดค่าสตริง 1.01 ให้กับ x แต่เราก็ยังสามารถเพิ่มค่าสตริงเข้าไปได้ เราไม่สามารถทำเช่นนี้ใน bash หรือ python ก่อนอื่น bash ไม่รองรับเลขคณิตทศนิยม และในขณะที่ bash มีตัวแปร "string" แต่ก็ไม่ใช่ "ง่าย" เพื่อดำเนินการใดๆ การดำเนินการทางคณิตศาสตร์ bash กำหนดให้เราต้องห่อการคำนวณของเราด้วยโครงสร้าง $() ที่น่าเกลียด ถ้าเราใช้ python เราจะต้องแปลงสตริง 1.01 ของเราให้เป็นค่าทศนิยมอย่างชัดเจนก่อนที่จะทำการคำนวณใดๆ กับมัน แม้จะไม่ใช่เรื่องยาก แต่ก็ยังอยู่ ขั้นตอนพิเศษ- ในกรณีของ awk ทั้งหมดนี้จะดำเนินการโดยอัตโนมัติ และทำให้โค้ดของเราสวยงามและสะอาดตา หากเราจำเป็นต้องยกกำลังสองให้กับฟิลด์แรกของแต่ละสตริงอินพุตและเพิ่มเข้าไป เราจะใช้สคริปต์ดังนี้:( print ($1^2)+1 )

หากคุณทดลองเพียงเล็กน้อย คุณจะพบว่าหากตัวแปรไม่มีตัวเลขที่ถูกต้อง awk จะถือว่าตัวแปรนั้นเป็นศูนย์ตัวเลขเมื่อประเมินนิพจน์ทางคณิตศาสตร์

ผู้ประกอบการจำนวนมาก

คุณสมบัติที่ดีอีกอย่างของ awk คือ ชุดสมบูรณ์ตัวดำเนินการทางคณิตศาสตร์ นอกเหนือจากการบวก การลบ การคูณ และการหารแบบมาตรฐานแล้ว awk ยังช่วยให้เราสามารถใช้ตัวดำเนินการเลขชี้กำลังที่แสดงไว้ก่อนหน้านี้ "^" ซึ่งเป็นตัวดำเนินการส่วนที่เหลือ การหารจำนวนเต็ม"%" และตัวดำเนินการมอบหมายงานที่สะดวกอื่น ๆ อีกมากมายที่ยืมมาจาก C.

ซึ่งรวมถึงตัวดำเนินการกำหนดก่อนและหลังส่วนเพิ่ม/ส่วนลด (i++, --foo) ตัวดำเนินการกำหนดที่มีการบวก/ลบ/คูณ/หาร (a+=3, b*=2, c/=2.2, d-=6.2) . แต่นั่นไม่ใช่ทั้งหมด เรายังมีตัวดำเนินการมอบหมายที่สะดวกด้วยการคำนวณส่วนที่เหลือของการหารจำนวนเต็มและการยกกำลัง (a^=2, b%=4)

เครื่องแยกสนาม

awk มีชุดตัวแปรพิเศษของตัวเอง บางคนทำให้มันเป็นไปได้ การปรับแต่งอย่างละเอียด awk ทำงานและอื่น ๆ ประกอบด้วย ข้อมูลอันมีค่าเกี่ยวกับการป้อนข้อมูล เราได้สัมผัสถึงหนึ่งในตัวแปรพิเศษเหล่านี้แล้ว ซึ่งก็คือ FS ตามที่กล่าวไว้ข้างต้น ตัวแปรนี้อนุญาตให้คุณระบุลำดับของอักขระที่ awk จะพิจารณาเป็นตัวคั่นฟิลด์ เมื่อเราใช้ /etc/passwd เป็นอินพุต FS จะถูกตั้งค่าเป็น /// ปรากฏว่าเพียงพอแล้ว แต่ FS ช่วยให้เรามีความยืดหยุ่นมากยิ่งขึ้น

ค่าของตัวแปร FS ไม่จำเป็นต้องเป็นอักขระตัวเดียว สามารถกำหนดนิพจน์ทั่วไปที่ระบุรูปแบบอักขระที่มีความยาวเท่าใดก็ได้ หากคุณกำลังประมวลผลฟิลด์ที่คั่นด้วยอักขระแท็บตั้งแต่หนึ่งตัวขึ้นไป FS จะต้องได้รับการกำหนดค่าดังนี้: FS="\t+"

ข้างบนเราใช้ ตัวละครพิเศษนิพจน์ทั่วไป "+" ซึ่งหมายถึง "มีอักขระก่อนหน้าหนึ่งรายการขึ้นไป"

หากเขตข้อมูลถูกคั่นด้วยช่องว่าง (อย่างน้อยหนึ่งช่องว่างหรือแท็บ) คุณอาจต้องการตั้งค่า FS ให้เป็นนิพจน์ทั่วไปต่อไปนี้:FS= "[[:space:]+]"

แม้ว่าการตั้งค่านี้จะใช้งานได้ แต่ก็ไม่จำเป็น ทำไม เนื่องจากค่าเริ่มต้นของ FS คืออักขระช่องว่างหนึ่งตัว ซึ่ง awk ตีความว่าเป็น "ช่องว่างหรือแท็บอย่างน้อยหนึ่งรายการ" ในตัวเรา ตัวอย่างที่เฉพาะเจาะจงค่า FS เริ่มต้นคือสิ่งที่เราต้องการอย่างแน่นอน!

นอกจากนี้ยังไม่มีปัญหากับนิพจน์ทั่วไปที่ซับซ้อนอีกด้วย แม้ว่าระเบียนจะถูกคั่นด้วยคำว่า "foo" ตามด้วยตัวเลขสามหลัก นิพจน์ทั่วไปต่อไปนี้จะแยกวิเคราะห์ข้อมูลอย่างถูกต้อง: FS="foo"

จำนวนฟิลด์

ตัวแปรสองตัวถัดไปที่เราจะดูโดยปกติไม่ได้มีไว้สำหรับเขียน แต่ใช้ในการอ่านและรับ ข้อมูลที่เป็นประโยชน์เกี่ยวกับการป้อนข้อมูล ตัวแปรแรกคือตัวแปร NF หรือที่เรียกว่าจำนวนฟิลด์ Awk จะตั้งค่าของตัวแปรนี้เป็นจำนวนฟิลด์ในบันทึกปัจจุบันโดยอัตโนมัติ คุณสามารถใช้ตัวแปร NF เพื่อแสดงเฉพาะบรรทัดอินพุตบางบรรทัด: NF == 3 ( พิมพ์ "มีสามฟิลด์ในรายการนี้: " $0 )

แน่นอนว่าตัวแปร NF ก็สามารถใช้ได้เช่นกัน คำสั่งแบบมีเงื่อนไข, ตัวอย่างเช่น:(

ถ้า (NF > 2) (

พิมพ์ $1 " " $2 : $3

บันทึกหมายเลข

ตัวแปรที่สะดวกอีกตัวหนึ่งคือหมายเลขบันทึก (NR) ประกอบด้วยหมายเลขของบันทึกปัจจุบันเสมอ (awk ถือว่าบันทึกแรกเป็นหมายเลขบันทึก 1) จนถึงตอนนี้เราได้จัดการกับ ไฟล์อินพุตซึ่งมีหนึ่งรายการต่อบรรทัด ในสถานการณ์เช่นนี้ NR จะรายงานหมายเลขบรรทัดปัจจุบันด้วย อย่างไรก็ตาม เมื่อเราเริ่มจัดการบันทึกหลายบรรทัดในบทความต่อๆ ไปของซีรีส์นี้ กรณีนี้จะไม่เป็นเช่นนั้นอีกต่อไป ดังนั้น ต้องใช้ความระมัดระวัง! สามารถใช้ NR เช่นเดียวกับตัวแปร NF เพื่อส่งออกเท่านั้น บางบรรทัดอินพุต:(NR< 10) || (NR >100) ( พิมพ์ "เราอยู่ในบันทึกหมายเลข 1-9 หรือ 101 ขึ้นไป")

อีกตัวอย่างหนึ่ง :(

ถ้า (NR > 10) (

พิมพ์ "ตอนนี้ข้อมูลจริงมาแล้ว!"

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

04.10.2015
16:55

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

วิธีเรียกใช้โปรแกรม awk

หากโปรแกรม awk ค่อนข้างง่ายและสั้น คุณสามารถพิมพ์โค้ดลงในคอนโซลได้โดยตรง:

อ๊าก"< код awk-программы >" < имя_файла_для_обработки >

คุณสามารถใช้มากกว่า awk เป็นอินพุต: ไฟล์ข้อความแต่ยังมีเอาต์พุตเข้าด้วย กระแสมาตรฐานแอปพลิเคชันอื่นๆ:

< некое_приложение >- อ๊าก"< код awk-программы >"

ในกรณีที่โค้ดโปรแกรม awk มีขนาดค่อนข้างใหญ่หรือต้องบันทึกไว้ ใช้ซ้ำสามารถเรียกได้จากไฟล์ด้วยสวิตช์ -f:

อ๊าค-ฟ< имя_файла_с_кодом_awk_программы > < имя_файла_для_обработки >

ในการทำการทดลอง เราใช้ไฟล์ test.cpp ซึ่งเราจะตรวจสอบผลลัพธ์ของโปรแกรม awk:

#รวม #รวม #รวม โมฆะ test1(); int test2(); // ความคิดเห็นสไตล์ C สำหรับฟังก์ชัน main() int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } // Комментарий в стиле С для функции test1() void test1() { std::cout << "Hello, test1!" << std::endl; } // Комментарий в стиле С для функции test2() int test2() { std::cout << "Hello, test2!" << std::endl; }

การโฆษณา

การกรองสตริงโดยใช้ awk

ก่อนอื่น awk ให้คุณเลือกบรรทัดจากข้อความตามนิพจน์ทั่วไปและเงื่อนไขตัวเลขบางอย่าง

การเลือกสตริงที่ตรงกับนิพจน์ทั่วไป

ตัวอย่างเช่น หากต้องการรับบรรทัดทั้งหมดในไฟล์ test.cpp ที่มีคำสั่ง #include preprocessor เราจะใช้คำสั่งต่อไปนี้:

Awk "/^#\s*รวม/" test.cpp

นิพจน์ทั่วไปเขียนระหว่างอักขระสองตัว / เป็นผลให้เราได้รับ:

#รวม #รวม #รวม

การเลือกสตริงที่ไม่ตรงกับนิพจน์ทั่วไป

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

แย่จัง "! /^[/](2).*/" test.cpp

นี่คือสิ่งที่เหลืออยู่:

#รวม #รวม #รวม โมฆะ test1(); int test2(); int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } void test1() { std::cout << "Hello, test1!" << std::endl; } int test2() { std::cout << "Hello, test2!" << std::endl; }

การเลือกแถวจากช่วงที่กำหนด

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

Awk "/^int .*(.*) (/, /^)/" test.cpp

ผลลัพธ์ที่เกี่ยวข้อง:

Int main(int argc, char** argv) ( std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { std::cout << i << std::endl; } return 0; } int test2() { std::cout << "Hello, test2!" << std::endl; }

การรวมเงื่อนไขการกรอง

หากต้องการตรวจสอบสตริงกับเงื่อนไขต่างๆ ในคราวเดียว ให้ใช้ตัวดำเนินการ && (AND) และ || (หรือ) .

คำสั่งต่อไปนี้จะพิมพ์ความคิดเห็นทั้งหมดที่ไม่มี main:

Awk "/[/](2).*/ && ! /main/" test.cpp

ด้วยเหตุนี้เราจึงมี:

// ความคิดเห็นสไตล์ C สำหรับฟังก์ชัน test1() // ความคิดเห็นสไตล์ C สำหรับฟังก์ชัน test2()

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

อ้าว”4< NR && NR < 7" test.cpp

NR เป็นตัวแปร awk ที่ระบุหมายเลขบรรทัด ดังนั้นโค้ดที่นำเสนอจะแสดงบรรทัดที่ 5 และ 6:

โมฆะ test1(); int test2();

การเลือกบรรทัดตามเงื่อนไขของแต่ละคำ

Awk สามารถกรองข้อความได้ไม่เพียงแต่ตามบรรทัดเท่านั้น แต่ยังกรองตามคำแต่ละคำด้วย คำที่ i ในบรรทัดสามารถอ้างอิงได้โดยใช้ $i การกำหนดหมายเลขเริ่มต้นที่หนึ่ง และ $0 กำหนดเนื้อหาของบรรทัดทั้งหมด จำนวนคำในบรรทัดถูกกำหนดโดยใช้ตัวแปร NF ดังนั้น $NF จึงชี้ไปที่คำสุดท้าย ตัวอย่างเช่น ลองค้นหาสตริงที่มีคำแรกเป็น int หรือ void:

Awk "$1 == "int" || $1 == "void"" test.cpp

เอาต์พุตคอนโซลที่สอดคล้องกัน:

โมฆะ test1(); int test2(); int main(int argc, char** argv) ( โมฆะ test1() ( int test2() (

อย่างไรก็ตาม การใช้การตรวจสอบนิพจน์ทั่วไปกับคำจะง่ายกว่า ในการดำเนินการนี้ awk จัดเตรียมตัวดำเนินการพิเศษ ~ ซึ่งจะต้องอยู่ระหว่างตัวแปรที่ชี้ไปที่คำและนิพจน์ทั่วไป ตามตัวอย่าง เราจะเขียนคำสั่งก่อนหน้านี้ใหม่ในรูปแบบที่กะทัดรัดมากขึ้น:

Awk "$1 ~ /int|void/" test.cpp

เลือกแถวตามลักษณะตัวเลข

ตัวดำเนินการทางคณิตศาสตร์ของภาษา C มีอยู่ใน awk ซึ่งให้อิสระแก่คุณในการดำเนินการ ตัวอย่างด้านล่างพิมพ์เส้นคู่ทั้งหมด (NR คือหมายเลขบรรทัด):

Awk "NR % 2 == 0" test.cpp

ผลลัพธ์ที่เกี่ยวข้อง:

#รวม int test2(); // ความคิดเห็นสไตล์ C สำหรับฟังก์ชัน main() std::cout<< "Hello, world!" << std::endl; for(int i = 0; i < 10; ++i) { } return 0; void test1() { } // Комментарий в стиле С для функции test2() std::cout << "Hello, test2!" << std::endl;

โปรแกรม awk ต่อไปนี้จะพิมพ์ทุกบรรทัดที่คำที่ 1 มีความยาวสามบรรทัด:

Awk "ความยาว ($1) == 3" test.cpp

เป็นผลให้เราได้รับ:

ทดสอบภายใน2(); int main(int argc, char** argv) ( int test2() (

Awk "NF == 2" test.cpp

และผลลัพธ์ที่สอดคล้องกัน:

#รวม #รวม #รวม โมฆะ test1(); int test2();

การโฆษณา

กลับ 0;

การทำงานกับสตริงใน awk

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

เอาต์พุตที่จัดรูปแบบแล้ว

Awk เทียบเท่ากับฟังก์ชัน C โดยตรง printf() ตามตัวอย่าง เราจะพิมพ์หมายเลขของมันไว้ที่ตอนต้นของแต่ละบรรทัด:

Awk "( printf "%-2d %s\n", NR, $0 )" test.cpp

นี่คือสิ่งที่เราได้รับ: 1#รวม 2#รวม 3#รวม<< "Hello, world!" << std::endl; 11 12 for(int i = 0; i < 10; ++i) { 13 std::cout << i << std::endl; 14 } 15 16 return 0; 17 } 18 19 // Комментарий в стиле С для функции test1() 20 void test1() { 21 std::cout << "Hello, test1!" << std::endl; 22 } 23 24 // Комментарий в стиле С для функции test2() 25 int test2() { 26 std::cout << "Hello, test2!" << std::endl; 27 }

4 5 โมฆะ test1(); 6 int test2(); 7 8 // ความคิดเห็นแบบ C สำหรับฟังก์ชัน main() 9 int main(int argc, char** argv) ( 10 std::cout

ฟังก์ชันการแปลง

นอกจาก printf() แล้ว awk ยังมีฟังก์ชันอื่นๆ อีกด้วย ตัวอย่างเช่น print() และ topper() :

ผลลัพธ์ที่เกี่ยวข้อง:

#รวม #รวม #รวม การทดสอบเป็นโมฆะ1(); INT ทดสอบ2(); // ความคิดเห็น C-STYLE สำหรับ MAIN () ฟังก์ชัน INT MAIN (INT ARGC, CHAR ** ARGV) ( STD :: COUT<< "HELLO, WORLD!" << STD::ENDL; FOR(INT I = 0; I < 10; ++I) { STD::COUT << I << STD::ENDL; } RETURN 0; } // КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST1() VOID TEST1() { STD::COUT << "HELLO, TEST1!" << STD::ENDL; } // КОММЕНТАРИЙ В СТИЛЕ С ДЛЯ ФУНКЦИИ TEST2() INT TEST2() { STD::COUT << "HELLO, TEST2!" << STD::ENDL; }

เงื่อนไข

คำสั่ง If-else มีอยู่ในโปรแกรม awk ตัวอย่างเช่น โค้ดต่อไปนี้พิมพ์โดยไม่ต้องเปลี่ยนบรรทัดที่มี int อยู่ในตำแหน่งที่ 1 และ ( อยู่ในตำแหน่งสุดท้าย มิฉะนั้น --- จะถูกส่งไปยังคอนโซล:

Awk " ( if($1 == "int" && $NF == "() print; else print "---" )" test.cpp

การรันโค้ดจะสร้างผลลัพธ์ต่อไปนี้:

Int หลัก (int argc, ถ่าน ** argv) ( --- --- --- --- --- --- --- --- --- --- --- --- --- -- - --- --- int test2() ( --- ---

ตัวแปร

ตัวแปรที่ไม่จำเป็นต้องประกาศก่อนก็มีอยู่ในโปรแกรม awk เช่นกัน รหัสต่อไปนี้สำหรับการนับจำนวนบรรทัดและคำในข้อความจะถูกวางไว้ในไฟล์ stat.awk:

( lineCount++; wordCount += NF ) END ( printf "จำนวนบรรทัด: %d, จำนวนคำ: %d\n", lineCount, wordCount )

แล้วจะเรียกว่าดังนี้:

Awk -f stat.awk ทดสอบ cpp

ผลการดำเนินการ:

จำนวนบรรทัด: 27 จำนวนคำ: 88

ตัวกรอง END ระบุว่าโค้ดในวงเล็บหลังจากนั้นควรดำเนินการหลังจากข้ามบรรทัดทั้งหมดแล้วเท่านั้น ตัวกรอง BEGIN ยังมีอยู่ใน awk ดังนั้นในกรณีทั่วไป โปรแกรมจะอยู่ในรูปแบบ:

BEGIN (ถูกเรียกก่อนที่การข้ามแถวจะเริ่มต้น) (ถูกเรียกสำหรับแต่ละแถวหลังส่วน BEGIN แต่ก่อนส่วน END) END (ถูกเรียกหลังจากการข้ามแถวเสร็จสมบูรณ์)

สุขา -lw test.cpp

รอบ

ในโปรแกรม awk คุณยังสามารถเข้าถึง C-style for และ while loops ได้ด้วย ตัวอย่างเช่น ลองพิมพ์บรรทัดทั้งหมดในลำดับย้อนกลับ มาสร้างไฟล์ Reverse.awk โดยมีเนื้อหาดังต่อไปนี้:

( for(i = NF; i > 0; --i) printf "%s ", $i; printf "\n" )

ลองเรียกโปรแกรมดังนี้:

Awk -f ย้อนกลับ awk test.cpp

เป็นผลให้คำในแต่ละบรรทัดจะถูกพิมพ์ในลำดับย้อนกลับ:

#รวม #รวม #รวม test1(); โมฆะ test2(); ฟังก์ชัน int main() สำหรับสไตล์ C ใน Comment // () argv char** argc, int main(int std::endl;<< world!" "Hello, << std::cout {) ++i 10; < i 0; = i int for(std::endl; << i << std::cout } 0; return } test1() функции для С стиле в Комментарий // { test1() void std::endl; << test1!" "Hello, << std::cout } test2() функции для С стиле в Комментарий // { test2() int std::endl; << test2!" "Hello, << std::cout }

ตัวแยกคำที่ไม่ได้มาตรฐาน

ตามค่าเริ่มต้น awk จะใช้อักขระช่องว่างเป็นตัวคั่นคำ แต่ลักษณะการทำงานนี้สามารถเปลี่ยนแปลงได้ เมื่อต้องการทำเช่นนี้ ให้ใช้สวิตช์ -F ตามด้วยบรรทัดที่กำหนดตัวคั่น ตัวอย่างเช่น โปรแกรมต่อไปนี้แสดงชื่อของกลุ่มและผู้ใช้ (หากกลุ่มมีผู้ใช้) จากไฟล์ /etc/group โดยใช้เครื่องหมายทวิภาคเป็นตัวคั่น:

Awk -F: "( if($4) printf "%15s: %s\n", $1, $4 )" /etc/group

การรวมตัวกรองและคำสั่งพิมพ์

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

ปล.อาซู | อ้าว "NR< 10 { print $1, $2, $NF }"

หลังจากเปิดตัวเราจะเห็น:

คำสั่ง USER PID รูท 1 /sbin/init รูท 2 รูท 3 รูท 5 รูท 7 รูท 8 รูท 9 รูท 10

ในบทความนี้ ฉันวางแผนที่จะแบ่งปันตัวอย่างที่เป็นประโยชน์ที่ช่วยฉันแก้ไขปัญหาในชีวิตประจำวัน และทำให้การใช้บรรทัดคำสั่งง่ายขึ้นอย่างมาก สำหรับผู้ที่ยังไม่คุ้นเคยกับ AWK ฉันขอแนะนำให้คุณเชี่ยวชาญภาษาสคริปต์นี้อย่างแน่นอน ไม่มีอะไรซับซ้อนเกี่ยวกับมัน ฉันวางแผนที่จะแนบแต่ละตัวอย่างพร้อมความคิดเห็นเล็กๆ น้อยๆ ที่ให้ความกระจ่างเกี่ยวกับความแตกต่างของการใช้โอเปอเรเตอร์บางตัว
.

เรากำลังมองหาบรรทัดที่มีพารามิเตอร์ ผูกที่อยู่ในไฟล์กำหนดค่า

root@debian:~# awk '/bind-address/' /etc/mysql/my.cnf
ที่อยู่ผูก = 127.0.0.1
ที่อยู่ผูก = 192.168.1.110

คำอธิบาย: AWK มีไวยากรณ์และตัวเลือกต่อไปนี้

โอเค[-f program_file | 'โปรแกรม'] [-Fdelimiter]
[-v ตัวแปร = ค่า] [ไฟล์ ... ]

-ฉค่า — กำหนดตัวคั่น (ตั้งค่าของตัวแปร FS ในตัว)
−ฉไฟล์ - ข้อความโปรแกรมถูกอ่านจากไฟล์ แทนที่จะเป็นบรรทัดคำสั่ง รองรับการอ่านจากหลายไฟล์
−v var=value - กำหนดค่าที่ต้องการให้กับตัวแปร
−− — เป็นจุดสิ้นสุดของรายการตัวเลือก

ตัวอย่างหมายเลข 2

ในตัวอย่างข้างต้น การค้นหาเสร็จสิ้นในไฟล์ แต่ AWK ก็สามารถยอมรับเอาต์พุตของคำสั่งอื่นได้เช่นกัน ลองทำให้ตัวอย่างของเราซับซ้อนตามนั้น

root@debian-wordpress:~# แมว /etc/mysql/my.cnf | awk '/ ผูกที่อยู่/'
ที่อยู่ผูก = 127.0.0.1
ที่อยู่ผูก = 192.168.1.110

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

ตัวอย่างหมายเลข 3

แสดงรายการลิงก์สัญลักษณ์และเส้นทางไปยังไฟล์เป้าหมาย

root@debian:~# ls -l /bin/ | awk '/lrwxrwxrwx/ (พิมพ์ $9, $10, $11)'
bzcmp -> bzdiff
bzegrep -> bzgrep
bzfgrep -> bzgrep
bzless -> bzmore
ไฟล์น้อย -> lesspipe
lsmod -> kmod
mt -> /etc/ทางเลือก/mt
nc -> /etc/ทางเลือก/nc
netcat -> /etc/alternatives/netcat
เปิด -> openvt
pidof -> /sbin/killall5
ทุบตี -> ทุบตี
นาโน -> นาโน
ช -> เส้นประ
sh.distrib -> เส้นประ

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

โดยทั่วไปสตริงอินพุตจะประกอบด้วยฟิลด์ที่คั่นด้วยช่องว่าง (การตั้งค่าเริ่มต้นนี้สามารถเปลี่ยนแปลงได้โดยใช้ตัวแปรบิวท์อิน เอฟเอสหรือตัวเลือก -F ตัวคั่น.) ช่องต่างๆ ถูกกำหนดให้เป็น $1, $2, …; $0 หมายถึงทั้งบรรทัด

ตัวอย่างหมายเลข 4

จากข้อมูลข้างต้น มาดูตัวอย่างการเปลี่ยนตัวคั่นเริ่มต้น - ดูรายชื่อผู้ใช้ทั้งหมดที่ไม่มีข้อมูลเพิ่มเติม

root@debian:~# awk -F ซิลิโคน '( พิมพ์ $1 )' /etc/passwd
ราก
ภูต
ถังขยะ
ระบบ
ซิงค์
เกม
ผู้ชาย

(เอาต์พุตคำสั่งสั้นลง)

คำอธิบาย: เนื่องจากอยู่ในไฟล์ /etc/passwdบันทึกจะถูกจัดเก็บในรูปแบบ " รูท:x:0:0:root:/root:/bin/bash" การเลือกโคลอนเป็นตัวคั่นและแสดงฟิลด์แรกสุดนั้นค่อนข้างสมเหตุสมผล ( $1 ) แต่ละบรรทัด ( $0 ).

ตัวอย่างหมายเลข 5

ทั้งหมดในไฟล์เดียวกันกับผู้ใช้ คุณสามารถนับจำนวนของพวกเขาได้

root@debian:~# awk 'END ( พิมพ์ NR )' /etc/passwd
25

คำอธิบาย: เทมเพลตพิเศษ เริ่มและ จบสามารถใช้ควบคุมก่อนอ่านบรรทัดอินพุตแรกและหลังจากอ่านบรรทัดอินพุตสุดท้ายตามลำดับ

Awk นั้นเป็นโปรแกรมแก้ไขสตรีมเช่น sed คุณสามารถไพพ์ข้อความลงในโปรแกรมนี้และสามารถจัดการทีละบรรทัดได้ โปรแกรมยังสามารถอ่านจากไฟล์ได้ Awk ก็เป็นภาษาโปรแกรมเช่นกัน โดยพื้นฐานแล้วหมายความว่า awk สามารถทำทุกอย่างที่ sed ทำได้และอีกมากมาย

awk ต่างจาก sed ตรงที่สามารถจำบริบท ทำการเปรียบเทียบ และอื่นๆ อีกมากมายที่ภาษาโปรแกรมอื่นสามารถทำได้ ตัวอย่างเช่น ไม่จำกัดเพียงบรรทัดเดียว ด้วยทักษะที่เหมาะสม มันสามารถเชื่อมต่อหลายบรรทัดได้

awk รูปแบบที่ง่ายที่สุดมีลักษณะดังนี้:

แย่ "(some_action ที่นี่)"

"Some_action_here" อาจเป็นนิพจน์ง่ายๆ ในการพิมพ์ผลลัพธ์ หรือสิ่งที่ซับซ้อนกว่านั้นมาก ไวยากรณ์คล้ายกับภาษาโปรแกรม "C" ตัวอย่างง่ายๆ:

แย่ "(พิมพ์ $1,$3)"

หมายถึงการพิมพ์คอลัมน์ที่หนึ่งและสาม โดยที่คอลัมน์หมายถึง "สิ่งที่คั่นด้วยช่องว่าง" ช่องว่าง = แท็บหรือช่องว่าง

ตัวอย่างสด:

ก้อง "1 2 3 4" | awk "(พิมพ์ $1,$3)" 1 3

ส่วนที่สอง: AWK ทำอะไรได้บ้าง

จุดประสงค์หลักในชีวิตของ AWK คือการจัดการอินพุตทีละบรรทัด โปรแกรม awk มักจะทำงานในลักษณะนี้

หากสิ่งที่คุณต้องการทำไม่เข้ากับโมเดลนี้ awk อาจไม่เหมาะกับแนวคิดของคุณ

ไวยากรณ์ปกติที่ใช้ในการเขียนโปรแกรม awk สามารถอธิบายได้ดังต่อไปนี้:

ตัวอย่าง Awk (คำสั่ง)

นี่หมายความว่า

“ดูแต่ละบรรทัดของอินพุตเพื่อดูว่ามี PATTERN อยู่ที่นั่นหรือไม่ หากมีให้เรียกใช้สิ่งที่อยู่ระหว่าง ()"

คุณสามารถข้าม SAMPLE หรือ COMMAND ได้

ถ้าคุณไม่ระบุรูปแบบ คำสั่งจะถูกนำไปใช้กับทุกบรรทัด

หากละเว้นคำสั่ง นี่จะเทียบเท่ากับการระบุ (เพียงพิมพ์บรรทัด):

(พิมพ์)

ตัวอย่างเฉพาะ:

Awk "/#/ (พิมพ์ "มีความคิดเห็นในบรรทัดนี้")" /etc/hosts

จะพิมพ์ "บรรทัดนี้มีความคิดเห็น" สำหรับทุกบรรทัดที่มีอย่างน้อยหนึ่ง "#" ที่ใดก็ได้บนบรรทัดใน /etc/hosts

ปรับเปลี่ยนเพื่อความชัดเจน

Awk "/#/ (พิมพ์ $0 ":\tมีความคิดเห็นในบรรทัดนี้)" /etc/hosts

องค์ประกอบ "//" ในรูปแบบเป็นวิธีหนึ่งในการระบุการจับคู่ ยังมีวิธีอื่นในการพิจารณาว่าสตริงตรงกันหรือไม่ ตัวอย่างเช่น,

Awk "$1 == "#" (พิมพ์ "บรรทัดขึ้นต้นด้วยแฮช")" /etc/hosts

จะจับคู่แถวที่มีคอลัมน์แรกเป็น "#" เดียว ลำดับของอักขระ "==" หมายถึงการจับคู่ที่ตรงกันของคอลัมน์แรกทั้งหมด

การปรับเปลี่ยนเพื่อความชัดเจน:

Awk "$1 == "#" (พิมพ์ $0 "\tline เริ่มต้นด้วยแฮช)" /etc/hosts

ในทางกลับกัน หากคุณต้องการให้คอลัมน์ใดคอลัมน์หนึ่งตรงกันบางส่วน ให้ใช้ตัวดำเนินการ "~"

Awk "$1 ~ /#/ (พิมพ์ "บางแห่งมีแฮชในคอลัมน์ 1")" /etc/hosts

โปรดจำไว้ว่าคอลัมน์แรกอาจอยู่หลังพื้นที่สีขาว

การปรับเปลี่ยนเพื่อความชัดเจน:

Awk "$1 ~ /#/ (พิมพ์ $0 "\t มีแฮชอยู่ที่ไหนสักแห่งในคอลัมน์ 1)" /etc/hosts

การป้อน "ความคิดเห็น" จะตรงกัน

การป้อน "ความคิดเห็น" จะตรงกันด้วย

หากคุณต้องการการจับคู่เฉพาะของ "สตริงที่ขึ้นต้นด้วย # และช่องว่าง" คุณจะใช้

อ้าว "/^# / (ทำอะไรสักอย่าง)"

การแข่งขันหลายรายการ

Awk จะประมวลผลรูปแบบทั้งหมดที่ตรงกับบรรทัดปัจจุบัน ดังนั้นหากเราใช้ตัวอย่างต่อไปนี้

Awk " /#/ (พิมพ์ "มีความคิดเห็น") $1 == "#" (พิมพ์ "ความคิดเห็นในคอลัมน์แรก") /^# / (พิมพ์ "ความคิดเห็นตั้งแต่เริ่มต้น") " /etc/hosts

สามรายการจะถูกส่งออกสำหรับบรรทัดดังต่อไปนี้:

#นี่คือความคิดเห็น

สองรายการสำหรับ

#นี่คือความคิดเห็นเยื้อง

และเพียงหนึ่งเดียวสำหรับ

1.2.3.4 ชื่อโฮสต์ # ความคิดเห็นสุดท้าย

การติดตามบริบท

สตริงทั้งหมดไม่ได้ถูกสร้างขึ้นเท่ากัน แม้ว่าจะดูเหมือนกันก็ตาม บางครั้งคุณอาจต้องการทำอะไรบางอย่างกับสตริงโดยขึ้นอยู่กับบรรทัดที่อยู่ข้างหน้า

ต่อไปนี้เป็นตัวอย่างสั้นๆ ที่พิมพ์บรรทัด "ADDR" หากคุณไม่ได้อยู่ในส่วน "ความลับ"

Awk " /secretstart/ ( secret=1) /ADDR/ ( if(secret==0) print $0 ) /* $0 คือบรรทัดเต็ม */ /secretend/ ( Secret=0) "

ข้อมูลต่อไปนี้จะพิมพ์เนื้อหาที่มี "ADDR" อยู่ข้างใน เว้นแต่จะเห็นสตริง "secretstart" เรื่องการสั่งซื้อ เช่น ถ้าคุณเขียนแบบนี้:

Awk " /ADDR/ ( if(secret==0) print $0 ) /* $0 เป็นบรรทัดที่สมบูรณ์ */ /secretstart/ ( Secret=1) /secretend/ ( Secret=0) "

และป้อนข้อมูลต่อไปนี้

ADDR addr ปกติ ลับเริ่ม ADDR addr ลับ ADDR ความลับอื่น addr ADDR ลับที่สาม ลับ ADDR ปกติด้วย

จากนั้น addr "ลับ" อันแรกจะถูกพิมพ์ เนื่องจากตัวอย่างดั้งเดิมจะซ่อนความลับไว้ทั้งสองอย่าง

ส่วนที่ 3: ตัวแปรพิเศษ

เราได้พูดคุยเกี่ยวกับไวยากรณ์ awk ตามปกติแล้ว ตอนนี้เรามาเริ่มดูสิ่งที่ทันสมัยกันดีกว่า

awk มีสตริงการจับคู่ "พิเศษ": " เริ่ม" และ " จบ"

คำสั่ง เริ่มเรียกหนึ่งครั้งก่อนที่จะอ่านแถวใดๆ จากข้อมูล และจะไม่เรียกอีกครั้ง

คำสั่ง จบเรียกว่าหลังจากอ่านทุกบรรทัดแล้ว หากได้รับหลายไฟล์ ระบบจะเรียกใช้หลังจากไฟล์ล่าสุดเสร็จสมบูรณ์แล้วเท่านั้น

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

BEGIN ( maxerrors=3 ; logfile=/var/log/something ; tmpfile=/tmp/blah) ... ( blah blah blah ) /^header/ ( headercount += 1 ) END ( printf("total headers counted=% d\n", จำนวนส่วนหัว);

ตัวอย่างนี้จะนับจำนวนครั้งที่ "ส่วนหัว" ปรากฏในไฟล์อินพุตและพิมพ์ผลรวมหลังจากการประมวลผลไฟล์ทั้งหมดเสร็จสิ้นแล้วเท่านั้น

AWK ยังมีค่าพิเศษอื่นๆ อีกมากมายที่คุณสามารถใช้ในส่วน ( ) ได้ ตัวอย่างเช่น,

พิมพ์ NF

จะให้จำนวนคอลัมน์ทั้งหมด (จำนวนฟิลด์) ในแถวปัจจุบัน FILENAMEจะเป็นชื่อไฟล์ปัจจุบัน หมายความว่าชื่อไฟล์ถูกส่งไปที่ awk แทนที่จะเป็นไปป์ที่กำลังใช้งาน

คุณไม่สามารถเปลี่ยนแปลงได้ เอ็นเอฟด้วยตัวเอง

เช่นเดียวกับตัวแปร NRซึ่งจะบอกคุณว่าคุณได้ประมวลผลไปแล้วกี่แถว (“จำนวนบันทึก” - จำนวนบันทึก)

มีตัวแปรพิเศษอื่นๆ แม้กระทั่งตัวแปรที่คุณสามารถเปลี่ยนได้ในระหว่างกลางโปรแกรม

ส่วนที่สี่: ตัวอย่าง Awk แบบง่าย

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

สำหรับตัวอย่างต่อไปนี้ เรามาสร้างไฟล์ field_data.txt ที่มีเนื้อหาดังต่อไปนี้:

ดอกกุหลาบเป็นสีแดง สีม่วงเป็นสีฟ้า น้ำตาลเป็นรสหวาน และคุณก็เช่นกัน

Echo -e "ดอกกุหลาบเป็นสีแดง\nสีม่วงเป็นสีฟ้า\nน้ำตาลมีรสหวาน\nและคุณก็เช่นกัน" >field_data.txt

มาสร้างไฟล์ letter.txt โดยมีเนื้อหาดังต่อไปนี้

A bb ccc dddd ggg hh ฉัน

บนบรรทัดคำสั่งคุณสามารถทำได้ดังนี้:

เสียงสะท้อน -e "a\nbb\nccc\ndddd\nggg\nhh\ni" > letter.txt

สุดท้ายนี้ เรามาสร้างไฟล์ข้อมูลเมลที่มีเนื้อหาดังต่อไปนี้:

อมีเลีย 555-5553 [ป้องกันอีเมล]เอฟ แอนโทนี่ 555-3412 [ป้องกันอีเมล]เบ็คกี้ 555-7685 [ป้องกันอีเมล]บิล 555-1675 [ป้องกันอีเมล]บรอเดอริก 555-0542 [ป้องกันอีเมล]อาร์ คามิลล่า 555-2912 [ป้องกันอีเมล]อาร์ ฟาเบียส 555-1234 [ป้องกันอีเมล]เอฟ จูลี่ 555-6699 [ป้องกันอีเมล]เอฟ มาร์ติน 555-6480 [ป้องกันอีเมล]ซามูเอล 555-3430 [ป้องกันอีเมล]ฌอง-ปอล 555-2127 [ป้องกันอีเมล]

ซึ่งสามารถทำได้บนบรรทัดคำสั่งดังนี้:

รับ https://raw.githubusercontent.com/tdhopper/awk-lessons/master/data/mail-data -O เมลดาต้า

รูปแบบเรียบง่าย (ตัวอย่าง)

หากเราต้องการบรรทัดที่ยาวเกินสองตัวอักขระและเราต้องการใช้การกระทำเริ่มต้น ( พิมพ์) จากนั้นเราจะได้:

Awk "ความยาว $0 > 2" ตัวอักษร.txt bb ccc dddd ggg hh

$0 เป็นตัวแปรบิวท์อินที่มีสตริง

ฟังก์ชั่นที่เรียบง่าย

หากเราไม่ระบุรูปแบบ แต่ละบรรทัดก็จะตรงกัน การดำเนินการเล็กน้อยคือการพิมพ์แต่ละบรรทัด:

Awk "( พิมพ์)" letter.txt a bb ccc dddd ggg hh i

การใช้ฟังก์ชัน ความยาวจากการกระทำของเรา เราจะได้ความยาวของแต่ละบรรทัด:

Awk "( ความยาวการพิมพ์ )" letter.txt 1 2 3 4 3 2 1

การดำเนินการนี้มีผลกับทั้งแถวโดยไม่มีเงื่อนไข นอกจากนี้เรายังสามารถระบุสิ่งนี้ได้อย่างชัดเจน:

Awk "( ความยาวการพิมพ์ $0 )" letter.txt 1a 2bb 3ccc 4dddd 3ggg 2hh 1i

Awk มีการควบคุมพิเศษสำหรับการรันโค้ดบางส่วนก่อนเริ่มอินพุตไฟล์และหลังไฟล์เสร็จสิ้น

Awk "BEGIN ( พิมพ์ "HI" ) ( พิมพ์ $0 ) END ( พิมพ์ "BYE!" )" letter.txt HI a bb ccc dddd ggg hh i BYE!

เราก็มีได้ องค์ประกอบเพิ่มเติมควบคุมระหว่างการพิมพ์โดยใช้ พิมพ์ฉ.

Awk "BEGIN ( printf "%-10s %s\n", "ชื่อ", "หมายเลข" \ printf "%-10s %s\n", "----", "------" ) \ ( printf "%-10s %s\n", $1, $2 )" หมายเลขชื่อข้อมูลเมล ---- ------ Amelia 555-5553 Anthony 555-3412 Becky 555-7685 Bill 555-1675 Broderick 555-0542 Camilla 555-2912 Fabius 555-1234 Julie 555-6699 Martin 555-6480 ซามูเอล 555-3430 Jean-Paul 555-2127

การรวมตัวอย่างและฟังก์ชัน

แน่นอนว่า รูปแบบและฟังก์ชันสามารถนำมารวมกันได้ เพื่อให้ฟังก์ชันนั้นถูกนำไปใช้เฉพาะในกรณีที่สตริงตรงกับรูปแบบเท่านั้น

เราสามารถพิมพ์ความยาวของทุกบรรทัดที่ยาวเกิน 2 ตัวอักษรได้

Awk "length($0) > 2 ( ความยาวการพิมพ์($0) )" letter.txt 3 4 3

จริงๆ แล้ว เราไม่จำเป็นต้องจำกัด Awk ไว้เพียงรูปแบบเดียว! เราสามารถมีรูปแบบได้ตามต้องการ โดยคั่นด้วยเครื่องหมายอัฒภาคหรือขึ้นบรรทัดใหม่:

Awk "length($0) > 2 ( พิมพ์ "Long: " length($0) ); length($0)< 2 { print "Short: " length($0) }" letters.txt Short: 1 Long: 3 Long: 4 Long: 3 Short: 1

ทุ่งนามากมาย

Awk ได้รับการออกแบบมาเพื่อการประมวลผลข้อมูลอย่างง่ายโดยมีหลายช่องติดต่อกัน ตัวคั่นฟิลด์สามารถระบุได้ด้วยคีย์ -ฟ.

ตัวอย่างของไฟล์ที่ตัวคั่นเป็นช่องว่าง:

Awk "( print )" field_data.txt ดอกกุหลาบเป็นสีแดง สีม่วงเป็นสีน้ำเงิน น้ำตาลเป็นรสหวาน และคุณก็เช่นกัน

หากเราระบุตัวคั่นฟิลด์ เราสามารถพิมพ์ฟิลด์ที่สองของแต่ละบรรทัดได้:

Awk -F " " "( print $2 )" field_data.txt เป็นเช่นนั้น

เราจะไม่ได้รับข้อผิดพลาดหากแถวไม่มีฟิลด์ที่ตรงกัน เราก็จะเห็นบรรทัดว่าง:

Awk -F " " "( พิมพ์ $4 )" field_data.txt คุณ

เนื่องจากตัวคั่นเริ่มต้นคือช่องว่าง คำสั่งก่อนหน้านี้จึงให้ผลลัพธ์ที่เหมือนกันทุกประการโดยไม่ต้องใช้ตัวเลือก -ฟ.สำหรับตัวอย่างที่มีความหมายมากขึ้น เรามาสร้างไฟล์อื่นกันดีกว่า อัตรา.txtโดยมีเนื้อหาดังนี้

พิลโครว์, ฮัมฟรีย์, 3 พิลโครว์, โซรา, 1 พลิเนียส, โอลโดน, 4 ราซเนียคกี้, แอนตัน, 7 รัสเซลล์, เบอร์ทรานด์, 0

ตอนนี้เราระบุว่าเป็นตัวคั่น , (ลูกน้ำ) และแสดงเนื้อหาของคอลัมน์ที่สอง:

Awk -F "," "( พิมพ์ $2 )" rates.txt Humphrey Zora Oldone Anton Bertrand

นิพจน์ตัวคั่นจะถูกตีความว่าเป็นนิพจน์ทั่วไป

Awk -F "((so)?are|is) " "(พิมพ์ "ฟิลด์ 1: " $1 "\nฟิลด์ 2: " $2)" field_data.txt ฟิลด์ 1: ดอกกุหลาบ ฟิลด์ 2: สีแดง ฟิลด์ 1: สีม่วง ฟิลด์ 2 : สีฟ้า สนาม 1: น้ำตาล ทุ่ง 2: หวาน ทุ่ง 1: และทุ่ง 2: คุณ

นิพจน์ทั่วไป

รูปแบบอาจเป็นนิพจน์ทั่วไป ไม่ใช่เพียงฟังก์ชันในตัว

เราสามารถใช้นิพจน์ทั่วไปเพื่อค้นหาคำทั้งหมดในโลก Unix ที่มีสระ 5 ตัวติดต่อกัน

Awk "/(5)/" /usr/share/dict/words cadiueio Chaouia euouae Guauaenok

การส่งผ่านตัวแปรไปยังโปรแกรม

ตัวเลือก -vสำหรับ Awk ให้เราส่งตัวแปรเข้าโปรแกรมได้ ตัวอย่างเช่น เราสามารถใช้สิ่งนี้กับค่าคงที่ฮาร์ดโค้ดได้

Awk -v pi=3.1415 "BEGIN ( พิมพ์ pi )" 3.1415

เรายังสามารถใช้ได้ -vเพื่อส่งตัวแปร Bash เป็นตัวแปร Awk

Awk -v user=$USER "BEGIN (ผู้ใช้การพิมพ์)" mial

นิพจน์ If-else

ถ้า-อย่างอื่นนิพจน์ใน Awk มีลักษณะดังนี้:

ถ้า (เงื่อนไข) ร่างกาย-แล้ว

ตัวอย่างเช่น:

พิมพ์f "1\n2\n3\n4" | awk \ "( \ if ($1 % 2 == 0) พิมพ์ $1, "เป็นคู่"; \ else พิมพ์ $1, "เป็นคี่" \ )" 1 เป็นคี่ 2 เป็นคู่ 3 เป็นคี่ 4 เป็นคู่

รอบ

Awk มีนิพจน์วนซ้ำหลายรายการ: ในขณะที่, ทำในขณะที่และ สำหรับ.

พวกเขามีไวยากรณ์ C ที่คาดหวัง

แย่\"BEGIN(\i=0;\ While(i< 5) { print i; i+=1; } \ }" 0 1 2 3 4 awk \ "BEGIN { \ i = 0; \ do { print i; i+=1; } while(i < 0) \ }" 0 awk \ "BEGIN { \ i = 0; \ for(i = 0; i<5; i++) print i \ }" 0 1 2 3 4

สำหรับยังสามารถกำหนดการวนซ้ำผ่านคีย์อาเรย์ ที่จะมีการหารือในภายหลัง

ส่วนที่ห้า: ฟังก์ชั่นการโทร

องค์ประกอบถัดไปของ AWK คือฟังก์ชันพิเศษในตัวทั้งหมด

AWK มีคุณสมบัติที่จะทำให้โปรแกรมเมอร์ภาษา C โดยเฉลี่ยค่อนข้างพอใจ ที่นี่มีสิ่งต่าง ๆ เช่น sin()/cos()/tan(), rand(),index(), sprintf(), tolower(), system()

ฟังก์ชั่นจะถูกจัดกลุ่มและสามารถดูได้ดังต่อไปนี้:

คณิตศาสตร์

+, -, /, *, sin(), cos(), tan(), atan(), sqrt(), แรนด์(), srand()

พวกเขาพูดเพื่อตัวเอง อย่างน้อยฉันก็อยากจะคิดอย่างนั้น

Awk -v pi=3.1415 "BEGIN ( print exp(1), log(exp(1)), sqrt(2), sin(pi), cos(pi), atan2(pi, 2) )" 2.71828 1 1.41421 9.26536 อี-05 -1 1.00387

โปรแกรมสามารถสร้างตัวเลขสุ่มในช่วง (0, 1)

ตามค่าเริ่มต้น Awk จะเริ่มต้นจากจุดเริ่มต้นเดียวกัน (เริ่มต้น) สำหรับ Awk การรันคำสั่งนี้สองครั้งติดต่อกันจะให้ผลลัพธ์เหมือนเดิม:

Awk "BEGIN ( พิมพ์ rand(); พิมพ์ rand() )" 0.237788 0.291066

หากต้องการตั้งค่าเริ่มต้น (seed) คุณสามารถใช้ฟังก์ชัน srand:

Awk "BEGIN ( srand(10); พิมพ์ rand(); พิมพ์ rand() )" 0.255219 0.898883 awk "BEGIN ( srand(10); พิมพ์ rand(); พิมพ์ rand() )" 0.255219 0.898883

การทำงาน ภายในส่งคืน "จำนวนเต็มที่ใกล้เคียงที่สุดกับ x ระหว่าง x ถึงศูนย์ โดยทิ้งศูนย์นำหน้า"

Awk "BEGIN ( พิมพ์ "int(0.9) = " int(0.9); พิมพ์ "int(-0.9) = " int(-0.9) )" int(0.9) = 0 int(-0.9) = 0

การจัดการสตริง

  • ดัชนี()จะบอกคุณว่า และถ้าเป็นเช่นนั้น สตริงเกิดขึ้นภายในสตริงย่อยหรือไม่ และหากเป็นเช่นนั้น
  • จับคู่()คล้ายกัน แต่ใช้ได้กับนิพจน์ทั่วไป
  • วิ่ง()ให้วิธีการจัดรูปแบบเอาต์พุตและทำการแปลงไปพร้อมกัน ใครก็ตามที่เคยใช้ printf() กับ C น่าจะคุ้นเคยกับสิ่งนี้ ตัวอย่างเช่น
newstring=sprintf("หนึ่งคือตัวเลข %d สองคือสตริง %s\n" หนึ่ง สอง);

"พิมพ์บรรทัดใหม่%d
"" บอกว่า "พิมพ์ค่าที่ตรงกับฉันเป็นเลขทศนิยม"%s

" บอกว่า "พิมพ์ค่าที่ตรงกับฉันเป็นสตริง"

เหล่านั้น. หากคุณต้องการเชื่อมสองบรรทัดเข้าด้วยกันโดยไม่มีตัวแบ่ง วิธีหนึ่งก็คือการใช้

  • Newstring=sprintf("%s%s", หนึ่ง, สอง)ความยาว()

การทำงาน เพียงให้วิธีง่ายๆ แก่คุณในการนับจำนวนอักขระในบรรทัดหากคุณต้องการส่วนย่อย (s, m, n) จะส่งคืนสตริงย่อยใน-ตัวละครเริ่มต้นจากตำแหน่ง นับจาก 1

Awk "( print $1, substr($1, 2, 3) )" field_data.txt ดอกกุหลาบ ose Violets iol Sugar uga และ nd

ดัชนี (s, t)ส่งกลับ `ตำแหน่งใน ซึ่งเส้นนั้นเกิดขึ้น ทีหรือ 0 ถ้ามันไม่เกิดขึ้น`

รูปแบบของดัชนีไม่ใช่นิพจน์ทั่วไป

Awk "( พิมพ์ $1, ดัชนี ($1, "s") )" field_data.txt ดอกกุหลาบ 3 สีม่วง 7 น้ำตาล 0 และ 0

การแข่งขัน (s, r)คืนตำแหน่งใน ซึ่งนิพจน์ทั่วไปเกิดขึ้น หรือ 0 หากไม่เกิดขึ้น ตัวแปร เริ่มต้นใหม่และ ความยาวถูกตั้งค่าเป็นตำแหน่งและความยาวของสตริงที่ตรงกัน

จับคู่- เป็นยังไงบ้าง ดัชนียกเว้นว่ารูปแบบนั้นเป็นนิพจน์ทั่วไป

Awk "( print $1, match($1, "") )" field_data.txt ดอกกุหลาบ 3 สีม่วง 7 น้ำตาล 1 และ 0 # "ค้นหาตัวอักษรที่ซ้ำกันสามตัวขึ้นไป" awk "( match($1, "(3)"); พิมพ์ $1, "\tรูปแบบเริ่มต้น:", RSTART, "\tรูปแบบสิ้นสุด:", RLENGTH )" letter.txt เริ่มต้นรูปแบบ: 0 สิ้นสุดรูปแบบ: -1 bb เริ่มต้นรูปแบบ: 0 สิ้นสุดรูปแบบ: -1 ccc เริ่มต้นรูปแบบ: 1 รูปแบบ สิ้นสุด: 3 dddd เริ่มต้นรูปแบบ: 1 สิ้นสุดรูปแบบ: 3 ggg เริ่มต้นรูปแบบ: 1 สิ้นสุดรูปแบบ: 3 hh เริ่มต้นรูปแบบ: 0 สิ้นสุดรูปแบบ: -1 i เริ่มต้นรูปแบบ: 0 สิ้นสุดรูปแบบ: -1

แยก (s, a, fs)แยกสตริงออกเป็นอาร์เรย์ขององค์ประกอบ a, a, …, a และส่งคืน จะส่งคืนสตริงย่อยใน.

การหารทำได้โดยนิพจน์ทั่วไป FSหรือมีเครื่องแยกสนาม เอฟเอส, ถ้า FSไม่ได้รับ สตริงว่างเป็นตัวคั่นฟิลด์แยกสตริงออกเป็นอาร์เรย์ขององค์ประกอบอักขระตามอักขระ

Awk "BEGIN ( print split("It-was_the-best_of-times", output_array, "[-_]"), output_array, output_array )" 6 ดีที่สุด

ย่อย (r, t, s)แทนที่ด้วย ทีการเกิดขึ้นครั้งแรกของนิพจน์ทั่วไป ในบรรทัด - หากไม่ได้รับ s ให้ใช้ $0

คือสตริงที่มีการแทนที่เกิดขึ้น แทนที่จะส่งคืนสตริงใหม่พร้อมกับการแทนที่ ระบบจะส่งคืนจำนวนการแทนที่ (0 หรือ 1)

Awk "BEGIN ( s = "มันเป็นช่วงเวลาที่ดีที่สุด มันเป็นช่วงเวลาที่แย่ที่สุด"; \ print "Num.matches ถูกแทนที่:", sub("times", "gifs", s); \ print s )" หมายเลข การแข่งขันถูกแทนที่: 1 มันเป็น GIF ที่ดีที่สุด มันเป็นช่วงเวลาที่แย่ที่สุด

gsubทำสิ่งเดียวกันกับ ย่อยยกเว้นว่านิพจน์ทั่วไปทั้งหมดจะถูกแทนที่ ย่อยและ gsubส่งคืนจำนวนการเปลี่ยน

Awk "BEGIN ( s = "มันเป็นช่วงเวลาที่ดีที่สุด มันเป็นช่วงเวลาที่แย่ที่สุด"; \ print "Num.matches ถูกแทนที่:", gsub("times", "cats", s); \ print s )" หมายเลข การแข่งขันที่ถูกแทนที่: 2 มันเป็นแมวที่ดีที่สุด มันเป็นแมวที่แย่ที่สุด sprintf sprintf(fmt, expr, ...) ส่งคืนสตริงที่เกิดจากการจัดรูปแบบ expr ... ตามรูปแบบ printf(3) fmt awk "BEGIN ( x = sprintf("[%8.3f]", 3.141592654); พิมพ์ x )" [ 3.142]

ฟังก์ชั่นระดับระบบ

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

เช่น น่ากลัว

ระบบ("rm -rf $HOME");

ระบบ ("/bin/kill 1")

หากคุณต้องการทำสิ่งที่ซับซ้อนกว่านี้ คุณก็อาจจะลงเอยด้วยการทำสิ่งที่คล้ายกัน

Sysstring=sprintf("คำสั่งบางอย่าง %s %s", arg1, arg2);

ระบบ(sysstring)ปิด() เป็นคุณสมบัติสำคัญที่มักถูกมองข้าม อาจเป็นเพราะไม่มีการโทรที่ชัดเจนเปิด() ระบบ(sysstring)นั่นคือสาเหตุที่ผู้คนไม่คิดถึงความท้าทาย

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

Awk ช่วยให้คุณสามารถเปิดไฟล์ที่ต้องการได้ทันที ตัวอย่างเช่น

/^ไฟล์/ ( พิมพ์ $3 >> $2 )

ควรใช้บรรทัด "file output here-is-a-word" เปิดไฟล์ "output" และพิมพ์ "here-is-a-word" ลงไป

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

/^file/ ( if ($2 != oldfile) ( close(oldfile) ); พิมพ์ $3 >> $2 ; oldfile = $2; )

ส่วนที่หก: อาร์เรย์

แนวคิดเรื่องอาร์เรย์

เราได้ดูตัวแปรเป็นชื่อที่มีค่าแล้ว อาร์เรย์เป็นส่วนขยายของตัวแปร อาร์เรย์เป็นตัวแปรที่มีค่ามากกว่าหนึ่งค่า สามารถมีได้มากกว่าหนึ่งค่า เนื่องจากแต่ละค่ามีตัวเลขของตัวเอง

หากคุณต้องการมีค่าสามค่า คุณสามารถพูดได้ว่า:

Value1="หนึ่ง"; value2="สอง"; value3="สาม";

หรือคุณสามารถใช้

ตัวอย่างแรกคือตัวแปรสามตัวที่มีชื่อเป็นของตัวเอง (ซึ่งแตกต่างกันด้วยอักขระหนึ่งตัว) ตัวอย่างที่สองคืออาร์เรย์ที่ประกอบด้วยตัวแปรหนึ่งตัว แต่มีหลายค่า ซึ่งแต่ละค่าจะมีหมายเลขของตัวเอง

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

ค่า = "หนึ่ง"; ค่า = "ค่าใหม่";

อย่างไรก็ตาม คุณสามารถกำหนดค่าใหม่ได้เหมือนกับที่คุณทำกับตัวแปรปกติ เหล่านั้น. ต่อไปนี้ถูกต้อง:

ค่า = "1"; พิมพ์ค่า; ค่า = "หนึ่ง"; พิมพ์ค่า;

สิ่งที่น่าสนใจคือ ไม่เหมือนกับภาษาอื่นๆ ตรงที่คุณไม่ได้ถูกบังคับให้ใช้แต่ตัวเลขเท่านั้น ในตัวอย่างข้างต้น ,, จริงๆ แล้วถูกตีความว่าเป็น [“1”], [“2”], [“3”] ซึ่งหมายความว่าคุณสามารถใช้สตริงอื่นๆ เป็นตัวระบุได้ และถือว่าอาร์เรย์เกือบจะเหมือนกับฐานข้อมูลคอลัมน์เดียว ชื่ออย่างเป็นทางการของสิ่งนี้คือ "อาร์เรย์ที่เกี่ยวข้อง"

ตัวเลข["หนึ่ง"]=1; ตัวเลข["สอง"]=2; พิมพ์หมายเลข["หนึ่ง"]; ค่า="two"; print numbers; value=$1; if(numbers = ""){ print "no such number"; } !}

เมื่อใดและอย่างไรจึงจะใช้อาร์เรย์

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

การบันทึกข้อมูลเพื่อใช้ในภายหลัง

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

/พิเศษ/( savewords=$2; lnum+=1; ) END ( count=0; while(savedwords != "") ( print count,savedwords; count+=1; ) )

แทนที่จะแสดงคำเพียงอย่างเดียว คุณสามารถใช้ส่วน END เพื่อดำเนินการเพิ่มเติมใดๆ ที่คุณอาจต้องการก่อนที่จะแสดงคำเหล่านั้น

หากคุณต้องการกำหนดดัชนีเฉพาะให้กับค่า (เพื่อหลีกเลี่ยงการซ้ำกัน) คุณสามารถอ้างอิงถึงค่าเหล่านั้นตามแถวของตัวเองได้ หรือ ตัวอย่างเช่น บันทึกอาร์เรย์ด้วยคอลัมน์ 3 ซึ่งจัดทำดัชนีโดยค่าที่สอดคล้องกันในคอลัมน์ 2

( threecol[$2]=$3 ) END ( สำหรับ (v ใน threecol) ( พิมพ์ v, threecol[v] ) )

อาร์เรย์และการแยก ()

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

นี่คือบรรทัดตัวแปร:field:type สามารถมีได้หลาย:type:values ​​​​ที่นี่

ในตัวอย่างข้างต้น ฟิลด์ที่สี่ซึ่งคั่นด้วยช่องว่าง มีฟิลด์ย่อยคั่นด้วยเครื่องหมายทวิภาค ตอนนี้ สมมติว่าคุณต้องการทราบค่าของฟิลด์ย่อยที่สองในฟิลด์ใหญ่ที่สี่ วิธีหนึ่งในการทำเช่นนี้คือการเรียก awks สองตัวที่เชื่อมต่อกันด้วยไปป์:

Awk "(พิมพ์ $4)" | awk -F: "(พิมพ์ $2)"

อีกวิธีหนึ่งคือการเปลี่ยนค่าของ "FS" ทันทีซึ่งมีตัวคั่นฟิลด์ (เห็นได้ชัดว่านี่ใช้ไม่ได้กับการใช้งาน awk ทั้งหมด):

Awk "( newline=$4; fs=FS; FS= Marriott; $0=newline; print $2 ; FS=fs; )"

แต่คุณสามารถทำได้กับอาร์เรย์โดยใช้ฟังก์ชัน split() ดังนี้:

Awk "( newline=$4; split(newline,subfields,"); print subfields) "

ในกรณีนี้ การใช้อาร์เรย์เป็นวิธีที่ธรรมดาที่สุดและอาจเป็นวิธีที่หรูหราที่สุด

ดังนั้น Awk จึงมีโครงสร้างข้อมูลจำนวนจำกัด นอกจากตัวแปรสเกลาร์และสตริงแล้ว ภาษายังมีโครงสร้างข้อมูลขนาดใหญ่ในตัวอีกด้วย แม้ว่าจะเรียกอย่างเป็นทางการว่า "อาร์เรย์" แต่จริงๆ แล้วโครงสร้างนี้เป็นอาร์เรย์ที่เกี่ยวข้องกัน คล้ายกับโครงสร้างข้อมูล dict ใน Python

ไม่จำเป็นต้องเริ่มต้นอาร์เรย์ คุณก็สามารถเริ่มกำหนดค่าได้ โปรดทราบว่าคีย์อาจเป็นตัวเลขหรือสตริงก็ได้

Awk "BEGIN ( \ a = 1.1; \ a = 0; \ a["DOG"] = "CAT"; \ print a, a, a["DOG"] \ )" 1.1 0 CAT

Awk จะไม่พิมพ์ตัวแปรโดยไม่มีดัชนี:

Awk "BEGIN ( \a["DOG"] = "CAT"; \print a\ )" awk: cmd. บรรทัด: 3: ร้ายแรง: พยายามใช้อาร์เรย์ `a" ในบริบทแบบสเกลาร์

แม้ว่าเราจะสามารถวนซ้ำโดยใช้คีย์ได้ สำหรับ:

Awk "BEGIN ( \ a = 1.1; \ a = 0; \ a["DOG"] = "CAT"; \ for(k in a) print(a[k]) \ )" CAT 0 1.1

ส่วนที่เจ็ด: AWK และเชลล์ (sh/ksh/bash/csh)

บางครั้งฟังก์ชันการทำงานของ AWK อาจไม่เพียงพอ ในกรณีนี้ คุณสามารถรวม awk เข้ากับเชลล์สคริปต์ของคุณได้ ด้านล่างนี้เป็นตัวอย่างของวิธีการดังกล่าว

สรุปง่ายๆ

บางครั้งคุณต้องการใช้ awk เช่นเดียวกับฟอร์แมตเตอร์ และดัมพ์เอาต์พุตไปยังผู้ใช้โดยตรง สคริปต์ต่อไปนี้ใช้ชื่อผู้ใช้เป็นอาร์กิวเมนต์ และใช้ awk เพื่อดัมพ์ข้อมูลของผู้ใช้จาก /etc/passwd

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

#!/bin/sh ในขณะที่ [ "$1" != "" ] ; ทำ awk -F: "$1 == ""$1"" ( พิมพ์ $1,$3) " /etc/passwd เปลี่ยนเสร็จแล้ว

การกำหนดตัวแปรเชลล์เอาต์พุต awk

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

โปรดสังเกตว่าเครื่องหมายคำพูดเดี่ยวถูกปิดในนิพจน์ awk อย่างไร หลังจากเครื่องหมายคำพูดปิด (ที่สอง) $1 คือตัวแปรที่ส่งผ่านค่าของอาร์กิวเมนต์แรกไปยังสคริปต์ ไม่ใช่ส่วนหนึ่งของไวยากรณ์ awk

#!/bin/sh user="$1" ถ้า [ "$user" == "" ] ; จากนั้นก้องข้อผิดพลาด: ต้องการชื่อผู้ใช้ ; ออก ; fi usershell=`awk -F: "$1 == ""$1"" ( พิมพ์ $7) " /etc/passwd` grep -l $usershell /etc/shells ถ้า [ $? -ne 0 ] ; จากนั้นก้องข้อผิดพลาด: เชลล์ $usershell สำหรับผู้ใช้ $user ไม่ได้อยู่ใน /etc/shells fi

ทางเลือกอื่น:

# ดู "man regex" usershell=`awk -F: "/^"$1":/ ( พิมพ์ $7) " /etc/passwd` echo $usershell; # เฉพาะ awk สมัยใหม่เท่านั้นที่ยอมรับ -v คุณอาจจำเป็นต้องใช้ "nawk" หรือ "gawk" usershell2=`awk -F: -v user=$1 "$1 == user ( print $7) " /etc/passwd` echo $usershell2;

การอธิบายวิธีการเพิ่มเติมข้างต้นถือเป็นการบ้านสำหรับผู้อ่าน :)

การถ่ายโอนข้อมูลไปยัง awk ผ่านทางไพพ์

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

#!/bin/sh grep -h " /index.html" $* | awk -F\" "(พิมพ์ $4)" | sort -u

  1. บทความที่น่าสนใจ ฉันอยากจะขอบคุณสำหรับความพยายามของคุณ

    ฉันพบว่ามันไม่ถูกต้อง หากคุณดำเนินการบรรทัดจากตัวอย่าง

    Awk -F " " "( พิมพ์ $2 )" field_data.txt

    มันจะส่งออกสิ่งเดียวกันกับ

    Awk "( พิมพ์ $2)" field_data.txt

    ผลลัพธ์ที่ได้คือตัวอย่างที่มี -ฟอธิบายไม่ถูก