เหตุใด awk จึงหยุดและรอถ้าชื่อไฟล์มี = และจะแก้ไขได้อย่างไร


คำตอบ:


19

ตามที่Chris บอกว่าข้อโต้แย้งของแบบฟอร์มvariablename=anythingนั้นจะถือว่าเป็นการกำหนดตัวแปร (ซึ่งจะดำเนินการในเวลาที่การประมวลผลมีข้อขัดแย้งซึ่งตรงข้ามกับ (ที่ใหม่กว่า) -v var=valueซึ่งจะดำเนินการก่อนBEGINข้อความ) แทนชื่อไฟล์อินพุต

นั่นอาจเป็นประโยชน์ในสิ่งต่าง ๆ เช่น:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

โดยที่คุณสามารถระบุFS/ RSต่อไฟล์ที่แตกต่างกัน มันยังใช้กันทั่วไปใน:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

เวอร์ชันที่ปลอดภัยกว่าของ:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(ซึ่งใช้งานไม่ได้หากfile1ว่างเปล่า)

แต่นั่นจะเข้าทางเมื่อคุณมีไฟล์ที่ชื่อมี=อักขระ

ตอนนี้เป็นเพียงปัญหาเมื่อสิ่งที่เหลืออยู่แรก=คือawkชื่อตัวแปรที่ถูกต้อง

สิ่งที่ถือว่าเป็นชื่อตัวแปรที่ถูกต้องในการเป็นที่เข้มงวดกว่าในawksh

POSIX ต้องการให้มันเป็นเช่น:

[_a-zA-Z][_a-zA-Z0-9]*

ด้วยอักขระเท่านั้นของชุดอักขระแบบพกพา อย่างไรก็ตาม/usr/xpg4/bin/awkอย่างน้อยที่สุดของ Solaris 11 นั้นไม่สอดคล้องกับเรื่องนั้นและอนุญาตให้ตัวอักษรใด ๆ ในสถานที่เกิดเหตุในชื่อตัวแปรไม่ใช่แค่ a-zA-Z

ดังนั้นอาร์กิวเมนต์เช่นx+y=fooหรือ=barหรือ./foo=barยังถือว่าเป็นชื่อไฟล์อินพุตและไม่ใช่การมอบหมายเนื่องจากสิ่งที่เหลืออยู่ของชื่อแรก=ไม่ใช่ชื่อตัวแปรที่ถูกต้อง ข้อโต้แย้งเช่นStéphane=Chazelas.txtอาจหรือไม่ขึ้นอยู่กับawkการนำไปใช้และสถานที่

ด้วยเหตุนี้ด้วย awk แนะนำให้ใช้:

awk '...' ./*.txt

แทน

awk '...' *.txt

เช่นเพื่อหลีกเลี่ยงปัญหาหากคุณไม่สามารถรับประกันชื่อของtxtไฟล์จะไม่มี=ตัวอักษร

นอกจากนี้ระวังว่าข้อโต้แย้งเช่น-vfoo=bar.txtนี้อาจถูกใช้เป็นตัวเลือกหากคุณใช้:

awk -f file.awk -vfoo=bar.txt

(ใช้ได้awk '{code}' -vfoo=bar.txtกับรุ่นawkจาก busybox ก่อน 1.28.0 ดูรายงานข้อผิดพลาดที่เกี่ยวข้อง )

อีกครั้งการใช้./*.txtงานรอบ ๆ ที่ (ใช้./คำนำหน้ายังช่วยด้วยไฟล์ที่เรียกว่า-เป็นอย่างอื่นซึ่งawkเข้าใจว่าหมายถึงอินพุตมาตรฐานแทน)

นั่นเป็นเหตุผล

#! /usr/bin/awk -f

shebangs ใช้งานไม่ได้จริงๆ ในขณะที่var=valueคนที่สามารถทำงานรอบ ๆ โดยการแก้ไขARGVค่า (เพิ่ม./คำนำหน้า) ในBEGINคำสั่ง:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

ที่จะไม่ช่วยตัวเลือกตามที่พวกเขาเห็นawkและไม่awkสคริปต์

ปัญหาหนึ่งที่อาจเกิดขึ้นกับเครื่องสำอางที่ใช้./คำนำหน้านั้นคือมันสิ้นสุดลงFILENAMEแต่คุณสามารถใช้substr(FILENAME, 3)เพื่อถอดออกได้หากคุณไม่ต้องการ

การนำ GNU ไปใช้awkแก้ไขปัญหาเหล่านั้นด้วย-Eตัวเลือก

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

มันออกแบบมาเป็นพิเศษสำหรับ:

#! /usr/bin/gawk -E

shebangs ที่รายการอาร์กิวเมนต์เป็นไฟล์อินพุตอยู่เสมอ (โปรดทราบว่าคุณยังคงมีอิสระในการแก้ไขARGVรายการนั้นในBEGINคำสั่ง)

คุณยังสามารถใช้เป็น:

gawk -e '...awk code here...' -E /dev/null *.txt

เราใช้-Eกับสคริปต์ที่ว่างเปล่า ( /dev/null) เพื่อให้แน่ใจว่าสิ่งเหล่า*.txtนั้นในภายหลังจะถูกใช้เป็นไฟล์อินพุตแม้ว่าจะมี=อักขระอยู่ก็ตาม


ฉันไม่เห็นว่าเส้นทางที่ชัดเจนที่ลงท้ายด้วย FILENAME เป็นปัญหาอย่างไร ทั้งสคริปต์ awk คือโดยทั่วไปในกรณีที่มันควรจะจัดการกับทุกชนิดของเส้นทางที่สิ้นสุดใน FILENAME (รวมถึง แต่ไม่ จำกัด../foo, /path/to/fooและเส้นทางที่อยู่ในการเข้ารหัสที่แตกต่างกัน) - ซึ่งในกรณีนี้substr(FILENAME,3)จะไม่เพียงพอหรือเป็น สคริปต์ shot หนึ่งที่ผู้ใช้โดยทั่วไปรู้ว่าชื่อไฟล์คืออะไรในกรณีที่ s / เขาอาจไม่ต้องรำคาญกับสิ่งใด ๆ ของพวกเขาที่มี=;-)
mosvy

2
@mosvy ฉันไม่คิดว่าจะระบุว่า./เป็นปัญหามาก แต่อาจไม่เป็นที่พึงปรารถนาภายใต้เงื่อนไขบางประการเช่นกรณีที่ชื่อไฟล์จะต้องรวมอยู่ในผลลัพธ์ซึ่งในกรณีนี้./ควรซ้ำซ้อนและไม่จำเป็นดังนั้นคุณ จะต้องกำจัดมันอย่างใด นี่คือตัวอย่างอย่างน้อยหนึ่งรายการ สำหรับผู้ใช้ที่รู้ว่าชื่อไฟล์คืออะไร - ในกรณีนี้เรายังรู้ว่าชื่อไฟล์คืออะไร แต่=ก็ยังได้รับการประมวลผลที่เหมาะสม ดังนั้นผู้นำสามารถ-เข้าไปในทาง
Sergiy Kolodyazhnyy

@mosvy ใช่แนวคิดก็คือคุณต้องการใช้./คำนำหน้าเพื่อแก้ไขawkคุณสมบัติ (mis) แต่สุดท้ายคุณก็จบลงด้วยสิ่งที่./อยู่บนเอาท์พุทซึ่งคุณอาจต้องการตัดออก ดูวิธีตรวจสอบว่าไฟล์บรรทัดแรกมีสตริงที่ระบุหรือไม่? ตัวอย่างเช่น.
Stéphane Chazelas

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

21

ในเวอร์ชันส่วนใหญ่ของ awk อาร์กิวเมนต์หลังจากโปรแกรมที่จะดำเนินการมีดังนี้:

  1. ไฟล์
  2. การมอบหมายของแบบฟอร์ม x=y

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

พฤติกรรมนี้มีการบันทึกไว้ใน POSIX :

อาร์กิวเมนต์สองประเภทต่อไปนี้สามารถผสมกันได้:

  • ไฟล์: ชื่อพา ธ ของไฟล์ที่มีอินพุตที่จะอ่านซึ่งจับคู่กับชุดรูปแบบในโปรแกรม หากไม่มีการระบุตัวถูกดำเนินการไฟล์หรือหากตัวถูกดำเนินการไฟล์เป็น '-' จะต้องใช้อินพุตมาตรฐาน
  • การมอบหมาย: ตัวถูกดำเนินการที่ขึ้นต้นด้วยตัวอักษรขีดล่างหรือตัวอักษรจากชุดอักขระแบบพกพา (ดูตารางในปริมาณคำจำกัดความฐานของ IEEE Std 1003.1-2001, ส่วน 6.1, ชุดอักขระแบบพกพา) ตามด้วยลำดับขีดล่างตัวเลข และตัวอักษรจากชุดอักขระแบบพกพาตามด้วยอักขระ '=' ต้องระบุการมอบหมายตัวแปรแทนชื่อพา ธ

ด้วยเหตุนี้คุณจึงมีตัวเลือกน้อย (# 1 น่าจะเป็นสิ่งที่รบกวนน้อยที่สุด):

  1. ใช้awk ... ./my=fileซึ่งก้าวเท้าข้างนี้เนื่องจาก.ไม่ใช่ "ตัวอักษรขีดล่างหรือตัวอักษรจากชุดอักขระแบบพกพา"
  2. ใส่ไฟล์บน stdin awk ... < my=fileใช้ อย่างไรก็ตามมันใช้งานไม่ได้กับหลายไฟล์
  3. สร้างฮาร์ดลิงก์ไปที่ไฟล์ชั่วคราวและใช้สิ่งนั้น คุณสามารถทำสิ่งที่ชอบln my=file my_fileแล้วใช้my_fileตามปกติ จะไม่มีการคัดลอกและไฟล์ทั้งสองจะได้รับการสำรองข้อมูลเดียวกันและข้อมูลเมตาของไอโหนด หลังจากใช้งานแล้วการลบลิงก์ที่สร้างขึ้นเนื่องจากจำนวนการอ้างอิงไปยัง inode จะยังคงมีค่ามากกว่า 0

6
ใช้งานไม่ได้./my=file ? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). สิ่งนี้ควรพกพาได้เพราะ./myไม่ใช่ชื่อตัวแปรที่ถูกต้องดังนั้นจึงไม่ควรแยกวิเคราะห์ด้วยวิธีนี้
Stephen Harris

2
ในฐานะที่เป็นว่าข้อความ POSIX กล่าวว่าปัญหาที่เกิดขึ้นเป็นเพียงเมื่อคนแรกที่=จะนำหน้าด้วยการขีดเส้นใต้หรือตัวอักษรตัวอักษรจากชุดตัวอักษรแบบพกพา (ดูตารางในปริมาณฐานความหมายของ IEEE Std 1,003.1-2,001 มาตรา 6.1 ชุดอักขระแบบพกพา) ตามลำดับของขีดตัวเลขและ alphabetics จากชุดตัวอักษรแบบพกพา ดังนั้นเส้นทางของไฟล์เช่น++foo=bar.txtหรือ=fooหรือ./foo=barจะ OK ทุกที่.หรือไม่ได้เป็น+ [_a-zA-Z]
Stéphane Chazelas

1
@SergiyKolodyazhnyy awk อยู่นอกเชลล์ดังนั้นมันจึงไม่สำคัญว่าคุณจะใช้อะไร ./my=fileจะถูกส่งผ่านคำต่อคำ
Chris Down

1
@SergiyKolodyazhnyy awk '{print $1,$2}' /etc/passwdสำหรับเดียวกัน ประเด็นก็คือว่าการที่เชลล์เปิดไฟล์นั้นตรงข้ามกับ awk นั้นไม่ได้สร้างความแตกต่างว่ามันทำให้มันค้นหาได้หรือไม่ ในความเป็นจริงawk '{exit}' < /etc/passwdคุณคาดหวังที่awkจะหาทางกลับไปยังจุดสิ้นสุดของการบันทึกแรกexitเพื่อให้แน่ใจว่ามันออกจากตำแหน่งภายใน stdin ที่นั่น POSIX ต้องการสิ่งนั้น /usr/xpg4/bin/awkไม่ก็บน Solaris แต่ไม่gawkว่ามิได้mawkดูเหมือนจะทำมันใน GNU / Linux
Stéphane Chazelas

3
@mosvy ดูที่ส่วนไฟล์อินพุตที่pubs.opengroup.org/onlinepubs/9699919799/utilities/…มันมีประโยชน์ในรูปแบบการใช้งานจำนวนมากที่มีความหมายกับไฟล์ปกติเช่นเมื่อคุณต้องการตัดทอนไฟล์หรือเขียนข้อมูลลงในที่ ตำแหน่งที่ระบุโดยawkวิธีนั้น
Stéphane Chazelas

3

ในการอ้างเอกสาร gawk (เพิ่มการเน้นข้อความ):

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

เหตุใดคำสั่งจึงหยุดและรอ เพราะในรูปแบบawk 'processing_script_here' my=file.txt ไม่มีไฟล์ที่ระบุโดยคำนิยามข้างต้น - my=file.txtถูกตีความว่าเป็นการกำหนดตัวแปรและหากไม่มีไฟล์ที่กำหนดไว้awkจะอ่าน stdin (ยังเห็นได้ชัดจากการstraceที่แสดงให้เห็นว่า awk ในคำสั่งดังกล่าวกำลังรอread(0,'...)syscall

นี่คือเอกสารในPOSIX awk สเปคดูส่วน OPERANDS และส่วนหนึ่งของการกำหนด )

การกำหนดตัวแปรมีความชัดเจนในawk '{print foo}' foo=bar /etc/passwdค่าที่fooถูกพิมพ์สำหรับทุกบรรทัดใน / etc / passwd การระบุ./foo=barหรือเส้นทางแบบเต็มใช้งานได้

โปรดทราบว่าการทำงานstraceบนawk '1' foo=barเช่นเดียวกับการตรวจสอบกับcat foo=barแสดงให้เห็นว่ามีปัญหานี้ awk เฉพาะและ execve ไม่แสดงชื่อไฟล์เป็นอาร์กิวเมนต์ผ่านเพื่อให้เปลือกหอยมีอะไรจะทำอย่างไรกับการกำหนดตัวแปร env ในกรณีนี้

นอกจากนี้โปรดทราบว่าawk '...script...' foo=barจะไม่ทำให้เกิดการสร้างตัวแปรสภาพแวดล้อมโดยเชลล์เนื่องจากการกำหนดค่าตัวแปรสภาพแวดล้อมควรอยู่ก่อนหน้าคำสั่งเพื่อให้มีผลบังคับใช้ ดูPOSIX กฎไวยากรณ์ของเชลล์หมายเลข 7 นอกจากนี้ยังสามารถตรวจสอบผ่านawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

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