ใน Perl ฉันจะอ่านไฟล์ทั้งหมดเป็นสตริงได้อย่างไร


118

ฉันกำลังพยายามเปิดไฟล์. html เป็นสตริงยาวขนาดใหญ่ นี่คือสิ่งที่ฉันมี:

open(FILE, 'index.html') or die "Can't read file 'filename' [$!]\n";  
$document = <FILE>; 
close (FILE);  
print $document;

ซึ่งส่งผลให้:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN

อย่างไรก็ตามฉันต้องการให้ผลลัพธ์มีลักษณะดังนี้:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

ด้วยวิธีนี้ฉันสามารถค้นหาทั้งเอกสารได้ง่ายขึ้น


8
ควรตรวจสอบว่าคำจำกัดความของ "Cant install" คืออะไรซึ่งเป็นปัญหาที่พบบ่อยและมักเป็นข้อโต้แย้งที่ไม่จำเป็นต้องทำ stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric

1
ฉันไม่สามารถแก้ไขอะไรในเซิร์ฟเวอร์ทั้งหมดที่สคริปต์นี้กำลังทำงานอยู่ได้นอกจากสคริปต์ที่เป็นตัว
goddamnyouryan

คุณไม่ได้รับอนุญาตให้เพิ่มไฟล์ใด ๆ ที่ใดก็ได้บนเซิร์ฟเวอร์?
Brad Gilbert

โมดูล FatPack ในสคริปต์ของคุณ? นอกจากนี้ดูเหมือนว่าคุณอาจคิดถึงการแยกวิเคราะห์ HTML ด้วยนิพจน์ทั่วไปอย่าทำ
MkV

คำตอบ:


82

เพิ่ม:

 local $/;

ก่อนอ่านจากที่จับไฟล์ ดูฉันจะอ่านทั้งไฟล์พร้อมกันได้อย่างไร , หรือ

$ perldoc -q "ทั้งไฟล์"

ดูตัวแปรที่เกี่ยวข้องกับ filehandlesในperldoc perlvarและperldoc -f local.

อนึ่งหากคุณสามารถวางสคริปต์ของคุณบนเซิร์ฟเวอร์คุณสามารถมีโมดูลทั้งหมดที่คุณต้องการได้ ดูฉันจะเก็บไดเร็กทอรีโมดูล / ไลบรารีของตัวเองได้อย่างไร .

นอกจากนี้เส้นทาง :: ชั้น :: ไฟล์ช่วยให้คุณสามารถslurpและคาย

เส้นทาง :: เล็ก ๆให้วิธีการความสะดวกสบายมากยิ่งขึ้นเช่นslurp, slurp_raw,slurp_utf8เช่นเดียวกับพวกเขาspewcounterparts


33
คุณน่าจะอธิบายได้ว่าผลกระทบจากการโลคัลไลซ์ $ / คืออะไรและจุดประสงค์ของมันคืออะไร
Danny

12
หากคุณไม่ต้องการอธิบายอะไรเกี่ยวกับการแปลเป็นภาษาท้องถิ่น$/คุณควรเพิ่มลิงก์สำหรับข้อมูลเพิ่มเติม
Brad Gilbert

7
คำอธิบายทีละขั้นตอนที่ดีเกี่ยวกับสิ่งที่ทำ: {local $ /; <$ fh>} มีให้ที่นี่: perlmonks.org/?node_id=287647
dawez

บางทีอาจจะเป็นเพียงแค่บอกว่าทำไมคุณต้องใช้และไม่ได้local my
Geremia

@Geremia การอภิปรายเรื่องขอบเขตอยู่นอกเหนือขอบเขตของคำตอบนี้
Sinan Ünür

99

ฉันจะทำเช่นนี้:

my $file = "index.html";
my $document = do {
    local $/ = undef;
    open my $fh, "<", $file
        or die "could not open $file: $!";
    <$fh>;
};

สังเกตการใช้เวอร์ชันสามอาร์กิวเมนต์ของ open ปลอดภัยกว่าอาร์กิวเมนต์สอง (หรือหนึ่ง) เวอร์ชันเก่ามาก สังเกตการใช้ filehandle คำศัพท์ด้วย ด้ามจับไฟล์คำศัพท์นั้นดีกว่ารูปแบบคำเปล่าแบบเก่าด้วยเหตุผลหลายประการ เรากำลังใช้ประโยชน์จากหนึ่งในนั้นที่นี่พวกเขาปิดเมื่ออยู่นอกขอบเขต


9
นี่อาจเป็นวิธีที่ไม่ใช้ cpan ที่ดีที่สุดในการทำเช่นนี้เนื่องจากใช้ทั้ง 3 อาร์กิวเมนต์เปิดตลอดจนเก็บตัวแปร INPUT_RECORD_SEPARATOR ($ /) ที่แปลเป็นบริบทที่ต้องการน้อยที่สุด
Danny

77

ด้วยFile :: Slurp :

use File::Slurp;
my $text = read_file('index.html');

ได้แม้คุณจะใช้ CPANได้


OP กล่าวว่าเขาไม่สามารถแก้ไขอะไรบนเซิร์ฟเวอร์ได้ ลิงก์ "ได้แม้ว่าคุณจะใช้ CPAN ได้" ที่นี่จะแสดงวิธีการแก้ไขข้อ จำกัด ดังกล่าวในกรณีส่วนใหญ่
เทรนตัน

Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry

2
@Dmitry - ติดตั้งโมดูล มีลิงก์คำแนะนำในการติดตั้งบนหน้าเมตาแพนที่ฉันเชื่อมโยงจากคำตอบนี้
Quentin

53

โพสต์ทั้งหมดไม่มีสำนวนเล็กน้อย สำนวนคือ:

open my $fh, '<', $filename or die "error opening $filename: $!";
my $data = do { local $/; <$fh> };

ส่วนใหญ่ไม่มีความจำเป็นต้องตั้ง $ / undefเพื่อ


3
local $foo = undefเป็นเพียงวิธีการแนะนำ Perl Best Practice (PBP) หากเรากำลังโพสต์ข้อมูลโค้ดฉันคิดว่าการพยายามอย่างเต็มที่เพื่อให้ชัดเจนน่าจะเป็นสิ่งที่ดี
Danny

2
การแสดงให้ผู้คนเห็นว่าการเขียนโค้ดแบบไม่ใช้สำนวนเป็นสิ่งที่ดีหรือไม่? หากฉันเห็น "local $ / = undef" ในโค้ดที่ฉันกำลังดำเนินการการกระทำแรกของฉันคือการทำให้ผู้เขียนอับอายต่อหน้าสาธารณชนใน irc (และโดยทั่วไปฉันไม่จู้จี้จุกจิกเกี่ยวกับปัญหา "สไตล์")
jrockway

1
ตกลงฉันจะกัด: อะไรคือสิ่งที่น่าเยาะเย้ยเกี่ยวกับ "$ / = undef" ในท้องถิ่น หากคำตอบเดียวของคุณคือ "มันไม่ใช่สำนวน" (a) ฉันไม่แน่ใจและ (b) แล้วยังไงต่อ? ฉันไม่แน่ใจนักเพราะมันเป็นเรื่องธรรมดามากที่จะทำเช่นนี้ แล้วจะเป็นอย่างไรเพราะมันชัดเจนและสั้นพอสมควร คุณอาจจู้จี้จุกจิกมากขึ้นเกี่ยวกับปัญหาสไตล์ที่คุณคิด
Telemachus

1
ที่สำคัญคือ "$ ท้องถิ่น /" เป็นส่วนหนึ่งของสำนวนที่รู้จักกันดี หากคุณกำลังเขียนโค้ดแบบสุ่มและเขียนว่า "local $ Foo :: Bar = undef;" ก็ใช้ได้ แต่ในกรณีพิเศษนี้คุณอาจพูดภาษาเดียวกันกับคนอื่น ๆ ได้เช่นกันแม้ว่าจะ "ชัดเจนน้อยกว่า" ก็ตาม (ซึ่งฉันไม่เห็นด้วยก็ตามพฤติกรรมของ "คนในท้องถิ่น" มีความหมายชัดเจนในแง่นี้)
jrockway

11
ขออภัยไม่เห็นด้วย เป็นเรื่องปกติมากที่จะมีความชัดเจนเมื่อคุณต้องการเปลี่ยนพฤติกรรมจริงของตัวแปรเวทมนตร์ เป็นการประกาศเจตจำนง แม้แต่เอกสารประกอบก็ใช้ 'local $ / = undef' (ดูperldoc.perl.org/perlsub.html#Tem Contemporary-Values-via-local () )
Leonardo Herrera

19

จากperlfaq5: ฉันจะอ่านทั้งไฟล์พร้อมกันได้อย่างไร? :


คุณสามารถใช้โมดูล File :: Slurp เพื่อทำในขั้นตอนเดียว

use File::Slurp;

$all_of_it = read_file($filename); # entire file in scalar
@all_lines = read_file($filename); # one line per element

วิธีการ Perl ตามธรรมเนียมสำหรับการประมวลผลบรรทัดทั้งหมดในไฟล์คือทำทีละบรรทัด:

open (INPUT, $file)     || die "can't open $file: $!";
while (<INPUT>) {
    chomp;
    # do something with $_
    }
close(INPUT)            || die "can't close $file: $!";

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

@lines = <INPUT>;

คุณควรคิดนานและหนักว่าทำไมคุณต้องโหลดทุกอย่างพร้อมกัน ไม่ใช่แค่โซลูชันที่ปรับขนาดได้ คุณอาจพบว่ามันสนุกกว่าที่จะใช้โมดูล Tie :: File มาตรฐานหรือการผูก $ DB_RECNO ของโมดูล DB_File ซึ่งช่วยให้คุณสามารถผูกอาร์เรย์กับไฟล์เพื่อให้การเข้าถึงองค์ประกอบอาร์เรย์เข้าถึงบรรทัดที่เกี่ยวข้องในไฟล์ได้จริง .

คุณสามารถอ่านเนื้อหาจัดการไฟล์ทั้งหมดเป็นสเกลาร์

{
local(*INPUT, $/);
open (INPUT, $file)     || die "can't open $file: $!";
$var = <INPUT>;
}

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

$var = do { local $/; <INPUT> };

สำหรับไฟล์ธรรมดาคุณยังสามารถใช้ฟังก์ชันอ่านได้

read( INPUT, $var, -s INPUT );

อาร์กิวเมนต์ที่สามทดสอบขนาดไบต์ของข้อมูลบน INPUT filehandle และอ่านจำนวนไบต์นั้นในบัฟเฟอร์ $ var


8

วิธีง่ายๆคือ:

while (<FILE>) { $document .= $_ }

อีกวิธีหนึ่งคือการเปลี่ยนตัวคั่นบันทึกอินพุต "$ /" คุณสามารถทำได้ภายในบล็อกเปล่าเพื่อหลีกเลี่ยงการเปลี่ยนตัวคั่นระเบียนส่วนกลาง

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}

1
มีปัญหามากมายกับทั้งสองตัวอย่างที่คุณให้มา ปัญหาหลักคือพวกเขาเขียนด้วยภาษา Perl โบราณฉันขอแนะนำให้อ่านModern Perl
Brad Gilbert

@ แบรดแสดงความคิดเห็นเมื่อหลายปีก่อนประเด็นยังคงยืนอยู่ ดีกว่าคือ{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger

@ Joel ที่ดีขึ้นเพียงเล็กน้อย. คุณไม่ได้ตรวจสอบการส่งออกของหรือที่เรียกว่าโดยปริยายopen . (นั่นยังมีปัญหาที่ไม่ระบุการเข้ารหัสอินพุต)closemy $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}
Brad Gilbert

use autodieการปรับปรุงที่สำคัญที่ฉันตั้งใจจะแสดงคือการจัดการไฟล์คำศัพท์และ 3 อาร์กิวเมนต์เปิด มีเหตุผลบางอย่างที่คุณกำลังทำdoสิ่งนี้หรือไม่ ทำไมไม่เพียงแค่ถ่ายโอนไฟล์ลงในตัวแปรที่ประกาศก่อนบล็อก?
Joel Berger

7

ตั้งค่า$/เป็นundef(ดูคำตอบของ jrockway) หรือเพียงแค่เชื่อมบรรทัดทั้งหมดของไฟล์:

$content = join('', <$fh>);

ขอแนะนำให้ใช้สเกลาร์สำหรับจัดการไฟล์ในเวอร์ชัน Perl ใด ๆ ที่รองรับ



3

คุณได้รับบรรทัดแรกจากตัวดำเนินการเพชร<FILE>เนื่องจากคุณกำลังประเมินในบริบทสเกลาร์:

$document = <FILE>; 

ในบริบทรายการ / อาร์เรย์ตัวดำเนินการเพชรจะส่งคืนทุกบรรทัดของไฟล์

@lines = <FILE>;
print @lines;

1
หมายเหตุเกี่ยวกับระบบการตั้งชื่อ: ตัวดำเนินการยานอวกาศคือ<=>และ<>เป็นผู้ดำเนินการเพชร
toolic

โอ้ขอบคุณฉันไม่เคยได้ยิน "ผู้ประกอบการเพชร" มาก่อนและคิดว่าทั้งคู่ใช้ชื่อเดียวกัน ฉันจะแก้ไขด้านบน
Nathan

2

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

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}

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

2
open f, "test.txt"
$file = join '', <f>

<f>- ส่งคืนอาร์เรย์ของบรรทัดจากไฟล์ของเรา (หาก$/มีค่าเริ่มต้น"\n") จากนั้นjoin ''จะติดอาร์เรย์นี้เข้าไป


2

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

# Bad! Don't do that!
my $content = do{local(@ARGV,$/)=$filename;<>};

มันกำหนดตัวคั่นบรรทัดใหม่ตามที่อธิบายไว้ก่อนหน้านี้ แต่ยังกำหนด STDIN ใหม่ด้วย

สิ่งนี้มีผลข้างเคียงอย่างน้อยหนึ่งอย่างที่ทำให้ฉันต้องเสียเวลาหลายชั่วโมงในการค้นหา: มันไม่ได้ปิดการจัดการไฟล์โดยนัยอย่างถูกต้อง (เนื่องจากไม่มีการเรียกcloseเลย)

ตัวอย่างเช่นการทำเช่นนั้น:

use strict;
use warnings;

my $filename = 'some-file.txt';

my $content = do{local(@ARGV,$/)=$filename;<>};
my $content2 = do{local(@ARGV,$/)=$filename;<>};
my $content3 = do{local(@ARGV,$/)=$filename;<>};

print "After reading a file 3 times redirecting to STDIN: $.\n";

open (FILE, "<", $filename) or die $!;

print "After opening a file using dedicated file handle: $.\n";

while (<FILE>) {
    print "read line: $.\n";
}

print "before close: $.\n";
close FILE;
print "after close: $.\n";

ผลลัพธ์ใน:

After reading a file 3 times redirecting to STDIN: 3
After opening a file using dedicated file handle: 3
read line: 1
read line: 2
(...)
read line: 46
before close: 46
after close: 0

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

while($. < $skipLines) {<FILE>};

เนื่องจากปัญหานี้เงื่อนไขเป็นเท็จเนื่องจากตัวนับบรรทัดไม่ได้รับการรีเซ็ตอย่างถูกต้อง ฉันไม่รู้ว่านี่เป็นบั๊กหรือรหัสผิด ... การเรียกclose;oder close STDIN;ก็ไม่ช่วยอะไร

ฉันแทนที่โค้ดที่อ่านไม่ได้นี้โดยใช้การเปิดการต่อสตริงและปิด อย่างไรก็ตามโซลูชันที่โพสต์โดย Brad Gilbert ยังใช้งานได้เนื่องจากใช้ตัวจัดการไฟล์ที่ชัดเจนแทน

สามบรรทัดที่จุดเริ่มต้นสามารถแทนที่ได้โดย:

my $content = do{local $/; open(my $f1, '<', $filename) or die $!; my $tmp1 = <$f1>; close $f1 or die $!; $tmp1};
my $content2 = do{local $/; open(my $f2, '<', $filename) or die $!; my $tmp2 = <$f2>; close $f2 or die $!; $tmp2};
my $content3 = do{local $/; open(my $f3, '<', $filename) or die $!; my $tmp3 = <$f3>; close $f3 or die $!; $tmp3};

ซึ่งจะปิดที่จับไฟล์อย่างถูกต้อง


2

ใช้

 $/ = undef;

ก่อนหน้า$document = <FILE>;นี้ $/คือตัวคั่นเร็กคอร์ดอินพุตซึ่งเป็นบรรทัดใหม่ตามค่าเริ่มต้น ด้วยการกำหนดใหม่เป็นundefคุณกำลังบอกว่าไม่มีตัวคั่นฟิลด์ เรียกว่าโหมด "slurp"

โซลูชันอื่น ๆ เช่นundef $/และlocal $/(แต่ไม่my $/) ประกาศซ้ำ $ / จึงให้ผลเช่นเดียวกัน



0

ฉันไม่รู้ว่ามันเป็นแนวทางปฏิบัติที่ดีหรือไม่ แต่ฉันเคยใช้สิ่งนี้:

($a=<F>);

-1

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

$x=`cat /tmp/foo`;    # note backticks, qw"cat ..." also works

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