Обкладіть один вкладиш, щоб додати файл


131

Це, мабуть, складне рішення .

Я шукаю такого простого оператора, як ">>", але для попереднього попередження.

Боюся, його не існує. Мені доведеться зробити щось на кшталт

 mv myfile tmp
 cat myheader tmp> myfile

Щось розумніше?


Що не так mktemp? Ви завжди можете очистити тимчасовий файл після цього ...
candu

Відповіді:


29

Хак нижче був швидкий непідготовлений відповідь , який працював і отримав багато upvotes. Потім, коли питання ставало все більш популярним та проходило все більше часу, обурені люди почали повідомляти, що це сортування спрацьовує, але дивні речі можуть статися, або це взагалі не спрацьовує, тому це було люто відхилено протягом певного часу. Така забава.

Рішення використовує точну реалізацію дескрипторів файлів у вашій системі, і оскільки реалізація значно відрізняється від ніксейсів, її успіх є повністю залежним від системи, остаточно не переносним, і на нього не слід покладатися ні на що навіть нечітко важливе.

Тепер на все, що не вийшло, відповідь була:


Створення іншого дескриптора файлу для запису файлу ( exec 3<> yourfile), після чого записується до цього ( >&3), здається, подолає дилему читання / запису на одному файлі. Працює для мене на 600K файлах з awk. Однак спробувати той самий трюк за допомогою "кота" не вдається.

Передача попередньої придатності як змінної для awk ( -v TEXT="$text") долає проблему буквальних цитат, яка заважає виконувати цей трюк із 'sed'.

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

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

3
УВАГА: перевірте вихід, щоб переконатися, що нічого не змінилося, окрім першого рядка. Я використав це рішення, і хоча це, здавалося, спрацювало, я помітив, що ніби додаються нові рядки. Я не розумію, звідки вони взялися, тому не можу бути впевнений, що це рішення справді має проблеми, але будьте обережні.
conradlee

1
Зауважте у прикладі bash вище, що подвійні лапки перебувають над поверненням каретки, щоб продемонструвати попередні кілька рядків. Переконайтеся, що оболонка та редактор тексту співпрацюють. \ r \ n спадає на думку.
Джон Мей

4
Використовуйте це з обережністю! Дивіться наступний коментар, чому: stackoverflow.com/questions/54365/…
Алекс

3
Якщо зараз це ненадійно, як щодо оновлення своєї відповіді кращим рішенням?
zakdances

Хак корисно , якщо і тільки якщо воно точно відомо , чому це працює , і, отже, при яких ретельно дотримуватися обмеження . Незважаючи на вашу відмову, ваш хак не відповідає цим критеріям ("повністю залежить від системи", "здається, подолано", "працює на мене"). Якщо ми серйозно сприймемо вашу відмову, ніхто не повинен використовувати ваш хак - і я згоден. То що нам залишається? Шумне відволікання. Я бачу два варіанти: (а) видалити свою відповідь, або (б) перетворити її на застереження, яке пояснює, чому - як спокусливо - такий підхід не може працювати загалом .
mklement0

102

Це все ще використовує тимчасовий файл, але принаймні він знаходиться в одному рядку:

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

Кредит: BASH: додати текст / рядки до файлу


4
Що робити -після cat?
макбол

Чи є спосіб додати "текст" без нового рядка після?
чишаку

@chishakuecho -n "text"
Sparhawk

3
Попередження: якщо yourfileце симпосилання, це не буде робити те, що ви хочете.
дшеферд

Чи відрізняється від цього відповідь: ехо "текст"> / tmp / out; котячий файл >> / tmp / out; mv / tmp / out yourfile ??
Річард


20

Джон Мі: ваш метод не гарантовано працює, і він, ймовірно, не вдасться, якщо ви додасте більше 4096 байт матеріалів (принаймні, це відбувається з gnu awk, але я припускаю, що інші реалізації матимуть подібні обмеження). У цьому випадку він не тільки не вдасться, але й увійде в нескінченний цикл, де буде читати власний вихід, тим самим змушуючи файл зростати, поки не заповниться весь наявний простір.

Спробуйте самі:

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

(попередження: знищити його через деякий час або воно заповнить файлову систему)

Крім того, дуже небезпечно редагувати файли таким чином, і це дуже погана порада, так як якщо щось відбувається під час редагування файлу (збій, диск повний), ви майже гарантовано залишите файл у непослідовному стані.


6
Це, мабуть, має бути коментар, а не окрема відповідь.
lkraav

10
@Ikraav: можливо, але "коментар" дуже довгий і складний (і корисний), він не виглядатиме добре і, мабуть, не відповідає обмеженій можливості коментаря StackOverflow.
Хенді Іраван

18

Неможливо без тимчасового файлу, але тут іде oneliner

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

Ви можете використовувати інші інструменти, такі як ed або perl, щоб зробити це без тимчасових файлів.


16

Можливо, варто відзначити, що часто є хорошою ідеєю безпечно генерувати тимчасовий файл за допомогою утиліти типу mktemp , принаймні, якщо сценарій коли-небудь буде виконуватися з root правами. Ви можете, наприклад, зробити наступне (знову в bash):

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

15

Якщо вам це потрібно на комп'ютерах, якими ви керуєте, встановіть пакет "moreutils" та використовуйте "губку". Тоді ви можете зробити:

cat header myfile | sponge myfile

Це добре спрацювало для мене в поєднанні з відповіддю @Vinko Vršalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Джессі Халлетт

13

Використовуючи bash heredoc, ви можете уникнути необхідності файлу tmp:

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

Це працює, тому що $ (cat myfile) оцінюється при оцінці сценарію bash до того, як буде виконана кішка з переспрямуванням.


9

припускаючи, що файл, який ви хочете відредагувати, - це мій.txt

$cat my.txt    
this is the regular file

І файл, який ви хочете додати - це заголовок

$ cat header
this is the header

Обов’язково в файлі заголовка є заключний порожній рядок.
Тепер ви можете додати його

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

Ви закінчите

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

Наскільки я знаю, це працює лише в «баш».


чому це працює? Чи є в дужках причиною того, що середня кішка буде виконана в окремій оболонці і одразу ж скидає весь файл?
Catskul

Я думаю, що <(cat my.txt) створює тимчасовий файл десь у файловій системі. Потім цей файл читається до зміни оригінального файлу.
cb0

Хтось одного разу показав мені, що можна зробити із синтаксисом "<()". Однак я ніколи не дізнавався, як називається цей метод, і я не зміг знайти щось на баш-сторінці. Якщо хтось знає більше про це, будь ласка, дайте мені знати.
cb0

1
Не працювало для мене. Не знаю чому. Я використовую GNU bash, версія 3.2.57 (1) -випуск (x86_64-apple-darwin14), я перебуваю на OS X Yosemite. Я закінчив два рядки this is the headerв моєму.txt. Навіть після того, як я оновив Bash, 4.3.42(1)-releaseя отримую той же результат.
Коханій Роберт

1
Bash не створює тимчасовий файл, він створює FIFO (трубу), до якої пише команда в процесі заміни ( <(...)) , тому немає гарантії, що my.txtчитається повністю на передній панелі , без якої ця техніка не буде працювати.
mklement0

9

Коли ви почнете намагатися робити речі, які утруднюються в оболонці-скрипті, я настійно рекомендую розглянути сценарій переписування сценарію "належною" мовою сценарію (Python / Perl / Ruby / тощо)

Що стосується попереднього попередження рядка до файлу, це неможливо зробити через трубопроводи, так як коли ви робите щось подібне cat blah.txt | grep something > blah.txt, воно ненароком видаляє файл. Існує невелика команда утиліти, яку spongeви можете встановити (ви робите cat blah.txt | grep something | sponge blah.txtі вона буферизує вміст файлу, а потім записує його у файл). Він схожий на тимчасовий файл, але робити це явно не потрібно. але я б сказав, що це "гірша" вимога, ніж, скажімо, Perl.

Це може бути спосіб зробити це через awk, або подібний, але якщо вам доведеться використовувати shell-скрипт, я думаю, що тимчасовий файл - це найпростіший (/ єдиний?) Спосіб ..


7

Як пропонує Даніел Велков, використовуйте трійник.
Для мене це просто розумне рішення:

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

7

EDIT: Це порушено. Дивіться дивну поведінку під час готування до файлу з котом і трійником

Для вирішення проблеми перезапису використовується tee:

cat header main | tee main > /dev/null

Повісили деякий час. Закінчено на "tee: main: На пристрої не залишилося місця". Основним було кілька ГБ, хоча обидва оригінальні текстові файли були лише декількома КБ.
Маркос

3
Якщо розроблене вами рішення порушено (і насправді заповнює жорсткі диски користувачів), зробіть спільноту послугу та видаліть свою відповідь.
aioobe

1
Чи можете ви вказати мені настанову, в якій сказано, що неправильні відповіді слід видалити?
Даніель Велков

3

Той, яким я користуюся. Цей дозволяє визначати порядок, додаткові символи тощо таким чином, як вам це подобається:

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

PS: тільки він не працює, якщо файли містять текст із зворотною косою рисою, тому що його інтерпретують як символи втечі


3

Здебільшого для розваг / гольф-снаряд, але

ex -c '0r myheader|x' myfile

зробить трюк, а трубопроводів чи переадресацій немає. Звичайно, vi / ex насправді не для неінтерактивного використання, тому vi коротко спалахне.


Найкращий рецепт з моєї точки зору, оскільки він використовує діючу команду і робить це прямо.
Дієго

2

Чому б не просто скористатися командою ed (як це вже запропоновано внизу)?

Ред читає весь файл у пам'яті та автоматично виконує редагування файлів на місці!

Отже, якщо ваш файл не такий величезний ...

# 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

Ще одним вирішенням буде використання ручок відкритого файлу, як запропонував Юрген Хьотцель для виведення перенаправлення від sed 's / c / d /' myFile до 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

Все це, звичайно, можна було б поставити на одну лінію.


2

Варіант рішення cb0 для "no temp file" для додавання фіксованого тексту:

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

Знову ж таки, це покладається на виконання під-оболонки - (..) - щоб уникнути відмови кішки мати один і той же файл для введення та виводу.

Примітка. Це рішення сподобалось. Однак у моєму Mac оригінальний файл втрачається (думав, що він не повинен, але це робить). Це можна виправити, написавши ваше рішення у вигляді: відлуння "текст для подачі" | cat - file_to_be_modified | кішка> tmp_file; mv tmp_file file_to_be_modified


1
Я теж вважаю оригінальний файл втраченим. Ubuntu Lucid.
Їжак



2

Попередження: для задоволення потреб ОП потрібно трохи більше попрацювати.

Незважаючи на його сумніви, повинен бути спосіб зробити підхід до роботи @shixilun. Повинна бути команда bash, щоб звільнити пробіл під час читання файлу в рядок заміщення sed (наприклад, замінити символи нового рядка на '\ n'. Команди оболонки visі catможуть працювати з недрукованими символами, але не пробілами, тому це не вирішить проблема:

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

виходить з ладу через неочищені нові рядки в сценарії замінника, які повинні бути попередньо заздалегідь символом продовження рядка () і, можливо, слідом за &, щоб зберегти оболонку і sed щасливими, як ця відповідь ТА

sed має обмеження розміру 40K для неглобальних команд пошуку-заміни (відсутнє трейлінг / г після шаблону), тому, швидше за все, уникнути страхітливих проблем із перекриттям буфера awk, про які анонім попереджав.


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

саме те, що я шукав, але насправді не правильна відповідь на питання
masterxilo

2

За допомогою $ (команда) ви можете записати результат команди в змінну. Тож я зробив це з трьох команд в одному рядку і без тимчасового файлу.

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

1

Якщо у вас є великий файл (у моєму випадку кілька сотень кілобайт) і доступ до python, це набагато швидше, ніж catрішення для труб:

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


1

Рішення з 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}"

Ви також можете зробити:

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

Але в цьому випадку ви повинні бути впевнені, що %ніде немає, включаючи вміст цільового файлу, так як це можна інтерпретувати та підкручувати ваші результати.


2
Це, здається, може підірвати (і скоротити ваш файл!), Якщо у вас є що-небудь у вашому цільовому файлі, що printf інтерпретує як варіант форматування.
Алан Х.

echoвидається більш безпечним варіантом. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Алан Х.

@AlanH. Я попереджую про небезпеку у відповіді.
користувач137369

Насправді ви попередили лише про відсотки в ${new_line}, а не цільовому файлі
Алан Х.

Чи можете ви бути більш чіткими? Це дійсно великий застереження ІМО. "Де завгодно" це не дуже ясно.
Алан Х.

1

Ви можете використовувати командний рядок perl:

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

Де -i створить вбудовану заміну файлу, а -0777 примружить весь файл та зробить ^ відповідним лише початку. -pe надрукує всі рядки

Або якщо my_header - це файл:

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

Якщо / e дозволить оцінювати код підстановки.


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

де "my_file" - файл, до якого слід додати "my_string".


0

Я люблячи @ fluffle в ред підході до кращого. Зрештою, командний рядок будь-якого інструменту перемикається на команди редакторів сценаріїв - це, по суті, те саме; не бачачи, щоб сценарій рішення редактора "чистота" був меншим чи ні.

Ось мій .git/hooks/prepare-commit-msgоднолінійковий додаток, щоб додати .gitmessageфайл in-repo для фіксації повідомлень:

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

Приклад .gitmessage:

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

Я роблю 1rзамість цього 0r, тому що це залишить порожній рядок готового до запису зверху файла з оригінального шаблону. Не ставте порожній рядок над вашим .gitmessageтоді, ви отримаєте два порожніх рядка. -sпригнічує діагностичний вихід інформації за ред.

У зв’язку з переглядом цього питання я виявив, що для vim-buffs також добре мати:

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

0

змінні, фут?

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

Я думаю, що це найчистіша версія редакції:

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

як функція:

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

cat myheader | prepend myfile

0

Якщо ви насправді пишете сценарії в BASH, ви можете просто видавати:

cat - ваш файл / tmp / out && mv / tmp / out ваш файл

Це насправді у складному прикладі, який ви самі розмістили у власному запитанні.


Редактор запропонував змінити це на cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, однак це досить відрізняється від моєї відповіді, що це має бути власна відповідь.
Тім Кеннеді

0

IMHO не існує оболонки (і ніколи не буде такої), яка б працювала послідовно та надійно незалежно від розмірів двох файлів myheaderі myfile. Причина полягає в тому, що якщо ви хочете це зробити, не повторюючи тимчасовий файл (і не дозволяючи оболонці мовчки повторюватися до тимчасового файлу, наприклад, через такі конструкції, як exec 3<>myfileпідключення до teeтощо.

"Реальне" рішення, яке ви шукаєте, повинно співпадати з файловою системою, і тому воно недоступне в просторі користувачів і буде залежати від платформи: ви просите змінити покажчик файлової системи у використанні myfileна поточне значення вказівника файлової системи для myheaderі замінити в файлової системі EOFз myheaderз ланцюгом , посиланнями на поточну адресу файлової системи зазначених вище myfile. Це не банально, і очевидно, це не може зробити не суперпользователь, і, мабуть, не і суперпользователь ... Пограйте з введеннями тощо.

Однак ви можете більш-менш підробити це за допомогою циклічних пристроїв. Дивіться, наприклад цей потік SO .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.