Shell một lớp lót để thêm vào một tập tin


131

Đây có lẽ là một giải pháp phức tạp .

Tôi đang tìm kiếm một toán tử đơn giản như ">>", nhưng để trả trước.

Tôi sợ nó không tồn tại. Tôi sẽ phải làm một cái gì đó như

 mv myfile tmp
 mèo myheader tmp> myfile

Bất cứ điều gì thông minh hơn?


Có chuyện gì với bạn mktempvậy? Bạn luôn có thể dọn sạch tệp tạm thời sau đó ...
candu 3/03/2015

Câu trả lời:


29

Vụ hack dưới đây là một câu trả lời nhanh chóng có hiệu quả và nhận được rất nhiều sự ủng hộ. Sau đó, khi câu hỏi trở nên phổ biến hơn và thời gian trôi qua, những người bị xúc phạm bắt đầu báo cáo rằng nó có hiệu quả nhưng những điều kỳ lạ có thể xảy ra, hoặc nó hoàn toàn không hoạt động, vì vậy nó đã bị hạ bệ trong một thời gian. Vui như vậy.

Giải pháp khai thác việc triển khai chính xác các mô tả tệp trên hệ thống của bạn và, vì việc triển khai thay đổi đáng kể giữa các nixes, nên thành công của nó hoàn toàn phụ thuộc vào hệ thống, chắc chắn không thể mang theo và không nên dựa vào bất cứ điều gì thậm chí quan trọng.

Bây giờ, với tất cả những gì ngoài câu trả lời là:


Tạo một mô tả tệp khác cho tệp ( exec 3<> yourfile) từ đó ghi vào đó ( >&3) dường như khắc phục được tình trạng đọc / ghi trên cùng một tình huống khó xử. Hoạt động với tôi trên các tệp 600K với awk. Tuy nhiên, thử cùng một mẹo sử dụng 'mèo' không thành công.

Vượt qua phần bổ sung dưới dạng một biến để awk ( -v TEXT="$text") khắc phục vấn đề trích dẫn theo nghĩa đen, điều này ngăn cản việc thực hiện thủ thuật này với 'sed'.

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3

3
CẢNH BÁO: kiểm tra đầu ra để đảm bảo không có gì thay đổi ngoài dòng đầu tiên. Tôi đã sử dụng giải pháp này và mặc dù nó có vẻ hiệu quả, tôi nhận thấy rằng một số dòng mới dường như được thêm vào. Tôi không hiểu những thứ này đến từ đâu, vì vậy tôi không thể chắc chắn rằng giải pháp này thực sự có vấn đề, nhưng hãy cẩn thận.
conradlee

1
Lưu ý trong ví dụ bash ở trên rằng dấu ngoặc kép kéo dài trên lợi nhuận vận chuyển để thể hiện việc chuẩn bị nhiều dòng. Kiểm tra trình bao và trình soạn thảo văn bản của bạn đang được hợp tác. \ r \ n đến với tâm trí.
John Mee

4
Sử dụng một cách thận trọng! Xem nhận xét sau để biết lý do: stackoverflow.com/questions/54365/ từ
Alex

3
Nếu bây giờ không đáng tin cậy, làm thế nào về việc cập nhật câu trả lời của bạn với một giải pháp tốt hơn?
zakdances

Một hack là hữu ích nếu và chỉ khi nó được biết chính xác tại sao nó hoạt động và, do đó, dưới những ràng buộc được quan sát cẩn thận . Từ chối trách nhiệm của bạn mặc dù, hack của bạn không đáp ứng các tiêu chí này ("hoàn toàn phụ thuộc vào hệ thống", "dường như khắc phục", "hoạt động cho tôi"). Nếu chúng tôi nghiêm túc từ chối trách nhiệm của bạn, không ai nên sử dụng bản hack của bạn - và tôi đồng ý. Vì vậy, những gì chúng ta còn lại? Một sự xao lãng ồn ào. Tôi thấy có hai lựa chọn: (a) xóa câu trả lời của bạn hoặc (b) biến nó thành một câu chuyện cảnh báo giải thích tại sao - hấp dẫn như có thể - cách tiếp cận này không thể hoạt động nói chung .
mkuity0

102

Điều này vẫn sử dụng tệp tạm thời, nhưng ít nhất nó nằm trên một dòng:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

Tín dụng: BASH: Chuẩn bị một văn bản / dòng vào một tệp


4
Làm gì -sau cat?
makbol

Có cách nào để thêm "văn bản" mà không có dòng mới sau không?
chishaku

@chishakuecho -n "text"
Sparhawk

3
Một cảnh báo: nếu yourfilelà một liên kết tượng trưng thì điều này sẽ không làm những gì bạn muốn.
dshepherd

Là câu trả lời khác nhau cho điều này: echo "text"> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out yourfile ??
Richard


20

John Mee: phương pháp của bạn không được đảm bảo để hoạt động và có thể sẽ thất bại nếu bạn nạp trước hơn 4096 byte nội dung (ít nhất đó là những gì xảy ra với gnu awk, nhưng tôi cho rằng các triển khai khác sẽ có các ràng buộc tương tự). Nó không chỉ thất bại trong trường hợp đó, mà nó sẽ đi vào một vòng lặp vô tận, nơi nó sẽ đọc đầu ra của chính nó, do đó làm cho tệp phát triển cho đến khi tất cả không gian có sẵn được lấp đầy.

Hãy thử nó cho chính mình:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(cảnh báo: giết nó sau một thời gian hoặc nó sẽ lấp đầy hệ thống tập tin)

Hơn nữa, rất nguy hiểm khi chỉnh sửa các tệp theo cách đó và đó là lời khuyên rất tệ, như thể có điều gì đó xảy ra trong khi tệp đang được chỉnh sửa (sự cố, đĩa đầy) bạn gần như được đảm bảo để lại tệp ở trạng thái không nhất quán.


6
Đây có lẽ nên là một bình luận, không phải là một câu trả lời riêng biệt.
lkraav

10
@Ikraav: có thể, nhưng "bình luận" rất dài và công phu (và hữu ích), nó sẽ không đẹp và có lẽ không phù hợp với khả năng bình luận hạn chế của StackOverflow.
Hendy I Girls

18

Không thể không có tệp tạm thời, nhưng ở đây có một oneliner

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

Bạn có thể sử dụng các công cụ khác như ed hoặc perl để làm điều đó mà không cần tệp tạm thời.


16

Có thể đáng lưu ý rằng thường là một ý tưởng tốt để tạo tệp tạm thời một cách an toàn bằng cách sử dụng một tiện ích như mktemp , ít nhất là nếu tập lệnh sẽ được thực thi với quyền root. Ví dụ, bạn có thể làm như sau (một lần nữa trong bash):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )

15

Nếu bạn cần điều này trên các máy tính mà bạn điều khiển, hãy cài đặt gói "moreutils" và sử dụng "miếng bọt biển". Sau đó, bạn có thể làm:

cat header myfile | sponge myfile

Điều này làm việc tốt cho tôi khi kết hợp với câu trả lời của @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

Sử dụng bash heredoc bạn có thể tránh sự cần thiết của tệp tmp:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

Điều này hoạt động vì $ (cat myfile) được đánh giá khi tập lệnh bash được đánh giá, trước khi con mèo có chuyển hướng được thực thi.


9

giả sử rằng tệp bạn muốn chỉnh sửa là my.txt

$cat my.txt    
this is the regular file

Và tệp bạn muốn thêm vào là tiêu đề

$ cat header
this is the header

Hãy chắc chắn để có một dòng trống cuối cùng trong tệp tiêu đề.
Bây giờ bạn có thể đăng ký trước với

$cat header <(cat my.txt) > my.txt

Bạn kết thúc với

$ cat my.txt
this is the header
this is the regular file

Theo tôi biết điều này chỉ hoạt động trong 'bash'.


tại sao điều này làm việc Các dấu ngoặc đơn có khiến con mèo giữa thực thi trong một shell riêng và đổ toàn bộ tệp cùng một lúc không?
Catskul

Tôi nghĩ rằng <(cat my.txt) tạo một tệp tạm thời ở đâu đó trong hệ thống tệp. Tập tin này sau đó được đọc trước khi tập tin gốc được sửa đổi.
cb0

Ai đó đã từng chỉ cho tôi những gì bạn có thể làm với cú pháp "<()". Tuy nhiên, tôi chưa bao giờ biết làm thế nào methode này được gọi và tôi cũng không thể tìm thấy thứ gì trong trang bash. Nếu ai đó biết thêm về nó xin vui lòng cho tôi biết.
cb0

1
Không làm việc cho tôi. Không biết tại sao. Tôi đang sử dụng GNU bash, phiên bản 3.2.57 (1) -release (x86_64-apple-darwin14), tôi đang dùng OS X Yosemite. Tôi đã kết thúc với hai dòng this is the headertrong my.txt. Ngay cả sau khi tôi cập nhật Bash để 4.3.42(1)-releasetôi cũng nhận được kết quả tương tự.
Kohányi Róbert

1
Bash không tạo ra một tệp tạm thời, nó tạo ra một FIFO (ống) mà lệnh trong quá trình thay thế quá trình ( <(...)) viết, do đó không có gì đảm bảo my.txtđược đọc đầy đủ , mà không có kỹ thuật này sẽ không hoạt động.
mkuity0

9

Khi bạn bắt đầu cố gắng làm những điều trở nên khó khăn trong shell-script, tôi sẽ khuyên bạn nên tìm cách viết lại tập lệnh bằng ngôn ngữ kịch bản "phù hợp" (Python / Perl / Ruby / etc)

Đối với việc chuẩn bị một dòng vào một tệp, bạn không thể thực hiện việc này thông qua đường ống, vì khi bạn làm bất cứ điều gì như thế cat blah.txt | grep something > blah.txt, nó vô tình làm trống tệp. Có một lệnh tiện ích nhỏ gọi là spongebạn có thể cài đặt (bạn làmcat blah.txt | grep something | sponge blah.txt và nó đệm nội dung của tệp, sau đó ghi nó vào tệp). Nó tương tự như một tệp tạm thời nhưng bạn không cần phải làm điều đó một cách rõ ràng. nhưng tôi sẽ nói rằng đó là một yêu cầu "tồi tệ" hơn, nói, Perl.

Có thể có một cách để làm điều đó thông qua awk, hoặc tương tự, nhưng nếu bạn phải sử dụng shell-script, tôi nghĩ rằng một tệp tạm thời là cách dễ nhất (/ chỉ?).


7

Giống như Daniel Velkov gợi ý, sử dụng tee.
Đối với tôi, đó là giải pháp thông minh đơn giản:

{ echo foo; cat bar; } | tee bar > /dev/null

7

EDIT: Điều này đã bị hỏng. Xem hành vi kỳ lạ khi chuẩn bị một tập tin với mèo và tee

Giải pháp cho vấn đề ghi đè đang sử dụng tee:

cat header main | tee main > /dev/null

Hùng một lúc. Kết thúc bằng "tee: main: Không còn chỗ trống trên thiết bị". chính là vài GB, mặc dù cả hai tệp văn bản gốc chỉ là vài KB.
Marcos

3
Nếu giải pháp bạn đã đăng bị hỏng (và trên thực tế sẽ lấp đầy ổ cứng của người dùng), hãy ủng hộ cộng đồng và xóa câu trả lời của bạn.
aioobe

1
Bạn có thể chỉ cho tôi một hướng dẫn nói rằng những câu trả lời sai nên bị xóa không?
Daniel Velkov

3

Một cái mà tôi sử dụng. Điều này cho phép bạn chỉ định thứ tự, ký tự thêm, vv theo cách bạn thích:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS: chỉ nó không hoạt động nếu các tệp chứa văn bản có dấu gạch chéo ngược, vì nó được hiểu là các ký tự thoát


3

Chủ yếu là cho vui / vỏ golf, nhưng

ex -c '0r myheader|x' myfile

sẽ thực hiện các mẹo, và không có đường ống hoặc chuyển hướng. Tất nhiên, vi / ex không thực sự cho sử dụng không tương tác, vì vậy vi sẽ nhanh chóng xuất hiện.


Công thức tốt nhất theo quan điểm của tôi vì nó sử dụng một lệnh đương nhiệm và thực hiện điều đó một cách đơn giản.
Diego

2

Tại sao không chỉ đơn giản là sử dụng lệnh ed (như đã được đề xuất bởi fluffle ở đây)?

ed đọc toàn bộ tập tin vào bộ nhớ và tự động thực hiện chỉnh sửa tập tin tại chỗ!

Vì vậy, nếu tệp của bạn không lớn ...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

Tuy nhiên, một cách giải quyết khác sẽ là sử dụng các tệp xử lý tệp mở như được đề xuất bởi Jürgen Hötzel trong đầu ra Redirect từ sed 's / c / d /' myFile sang myFile

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

Tất cả điều này có thể được đặt trên một dòng duy nhất, tất nhiên.


2

Một biến thể trên giải pháp của cb0 cho "không có tệp tạm thời" để thêm vào văn bản cố định:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

Một lần nữa, điều này phụ thuộc vào thực thi shell phụ - (..) - để tránh con mèo từ chối có cùng một tệp cho đầu vào và đầu ra.

Lưu ý: Thích giải pháp này. Tuy nhiên, trong máy Mac của tôi, tệp gốc bị mất (nghĩ rằng nó không nên nhưng nó có). Điều đó có thể được khắc phục bằng cách viết giải pháp của bạn dưới dạng: echo "văn bản để trả trước" | mèo - file_to_be_modified | mèo> tmp_file; mv tmp_file file_to_be_modified


1
Tôi cũng tìm thấy các tập tin ban đầu bị mất. Ubuntu Lucid.
Nhím

2

Đây là những gì tôi khám phá:

echo -e "header \n$(cat file)" >file

1
Điều này giống như đề xuất trước đó của @nemisj Không?
Nhím


2

CẢNH BÁO: việc này cần thêm một chút công việc để đáp ứng nhu cầu của OP.

Cần có một cách để làm cho cách tiếp cận sed của @shixilun hoạt động bất chấp những nghi ngờ của anh ta. Phải có lệnh bash để thoát khoảng trắng khi đọc tệp vào chuỗi thay thế sed (ví dụ: thay thế các ký tự dòng mới bằng '\ n'. Các lệnh Shell viscatcó thể xử lý các ký tự không thể in được, nhưng không phải khoảng trắng, vì vậy điều này sẽ không giải quyết được OP vấn đề:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

thất bại do các dòng mới thô trong tập lệnh thay thế, cần được thêm vào một ký tự tiếp tục dòng () và có lẽ theo sau là &, để giữ cho shell và sed hạnh phúc, như câu trả lời SO này

sed có giới hạn kích thước 40K cho các lệnh thay thế tìm kiếm không toàn cầu (không có dấu / g sau mẫu), do đó có thể sẽ tránh được các vấn đề tràn bộ đệm đáng sợ của awk mà cảnh báo ẩn danh đã cảnh báo.


2
sed -i -e "1s/^/new first line\n/" old_file.txt

chính xác những gì tôi đang tìm kiếm, nhưng thực sự không phải là câu trả lời đúng cho câu hỏi
masterxilo 19/03/18

2

Với $ (lệnh), bạn có thể viết đầu ra của lệnh thành một biến. Vì vậy, tôi đã làm nó trong ba lệnh trong một dòng và không có tệp tạm thời.

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile

1

Nếu bạn có một tệp lớn (vài trăm kilobyte trong trường hợp của tôi) và truy cập vào python, thì điều này nhanh hơn nhiều so với catcác giải pháp đường ống:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'


1

Một giải pháp với printf:

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

Bạn cũng có thể làm:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

Nhưng trong trường hợp đó, bạn phải chắc chắn rằng không có bất kỳ %nơi nào, kể cả nội dung của tệp mục tiêu, vì điều đó có thể được diễn giải và làm hỏng kết quả của bạn.


2
Điều này dường như sẽ nổ tung (và cắt bớt tệp của bạn!) Nếu bạn có bất cứ điều gì trong tệp mục tiêu của mình mà printf diễn giải như một tùy chọn định dạng.
Alan H.

echocó vẻ như là một lựa chọn an toàn hơn echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.

@AlanH. Tôi cảnh báo về những nguy hiểm trong câu trả lời.
dùng137369

Trên thực tế, bạn chỉ cảnh báo về phần trăm trong ${new_line}chứ không phải tệp mục tiêu
Alan H.

Bạn có thể rõ ràng hơn? Đó là một IMO thực sự lớn. Ở bất cứ nơi nào, Cameron không làm điều này rất rõ ràng.
Alan H.

1

Bạn có thể sử dụng dòng lệnh perl:

perl -i -0777 -pe 's/^/my_header/' tmp

Trong đó -i sẽ tạo một sự thay thế nội tuyến của tệp và -0777 sẽ làm lu mờ toàn bộ tệp và làm cho ^ chỉ khớp với phần đầu. -pe sẽ in tất cả các dòng

Hoặc nếu my_header là một tệp:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

Trường hợp / e sẽ cho phép một mã thay thế.


0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

trong đó "my_file" là tệp để thêm "my_opes" vào.


0

Tôi thích cách tiếp cận ed của fluffle là tốt nhất. Xét cho cùng, bất kỳ công cụ dòng lệnh nào của công cụ so với các lệnh biên tập theo kịch bản về cơ bản đều giống nhau ở đây; không thấy một giải pháp biên tập kịch bản "sạch sẽ" là ít hơn hoặc không có gì.

Đây là một lớp lót của tôi được thêm vào .git/hooks/prepare-commit-msgđể thêm vào một .gitmessagetệp in-repo để cam kết thông báo:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

Ví dụ .gitmessage:

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

Tôi đang làm 1rthay vì 0r, vì điều đó sẽ để lại dòng sẵn sàng để viết trống trên đầu tệp từ mẫu ban đầu. Đừng đặt một dòng trống lên trên của bạn .gitmessagesau đó, bạn sẽ kết thúc với hai dòng trống. -sngăn chặn đầu ra thông tin chẩn đoán của ed.

Liên quan đến việc trải qua điều này, tôi phát hiện ra rằng đối với vim-buff cũng rất tốt để có:

[core]
        editor = vim -c ':normal gg'

0

biến, ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list

0

Tôi nghĩ rằng đây là biến thể sạch nhất của ed:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

như một chức năng:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile

0

Nếu bạn đang viết kịch bản trong BASH, thực tế, bạn chỉ có thể phát hành:

mèo - yourfile / tmp / out && mv / tmp / out yourfile

Đó thực sự là trong Ví dụ phức tạp mà chính bạn đã đăng trong câu hỏi của riêng bạn.


Một biên tập viên đề nghị thay đổi điều này thành cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, tuy nhiên điều đó đủ khác với câu trả lời của tôi rằng đó phải là câu trả lời của riêng họ.
Tim Kennedy

0

IMHO không có giải pháp shell (và sẽ không bao giờ là một) sẽ hoạt động ổn định và đáng tin cậy bất kể kích thước của hai tệp myheadermyfile. Lý do là nếu bạn muốn làm điều đó mà không lặp lại một tệp tạm thời (và không để cho trình bao lặp lại âm thầm thành một tệp tạm thời, ví dụ như thông qua các cấu trúc như exec 3<>myfile, chuyển sangtee , v.v.

Giải pháp "thực" mà bạn đang tìm kiếm cần tìm hiểu về hệ thống tệp và do đó, nó không có sẵn trong không gian người dùng và sẽ phụ thuộc vào nền tảng: bạn đang yêu cầu sửa đổi con trỏ hệ thống tệp được sử dụng theo myfilegiá trị hiện tại của con trỏ hệ thống tệp cho myheadervà thay thế trong hệ thống tập tin với EOFcác myheadervới một liên kết bị xích vào địa chỉ hệ thống tập tin hiện tại chỉ bằngmyfile . Điều này không tầm thường và rõ ràng không thể được thực hiện bởi một người không phải là siêu người dùng, và có lẽ không phải bởi siêu người dùng đó ... Chơi với các nút, v.v.

Tuy nhiên, bạn có thể ít nhiều giả mạo điều này bằng cách sử dụng các thiết bị lặp. Xem ví dụ chủ đề SO này .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.