Оболочка одного вкладыша для добавления к файлу


131

Наверное, это сложное решение .

Я ищу простой оператор, такой как ">>", но для добавления.

Боюсь, этого не существует. Мне нужно сделать что-то вроде

 mv myfile tmp
 кошка myheader tmp> мой файл

Что-нибудь умнее?


Что не так с mktemp? Вы всегда можете очистить временный файл после этого ...
Канду 03

Ответы:


29

Приведенный ниже хакер был быстрым неожиданным ответом, который сработал и получил множество положительных отзывов. Затем, когда вопрос стал более популярным и прошло больше времени, возмущенные люди начали сообщать, что это вроде как работает, но могут происходить странные вещи, или это просто не работает вообще, поэтому какое-то время яростно отвергали его. Так весело.

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

Итак, со всем этим ответом был:


Создание другого файлового дескриптора для file ( exec 3<> yourfile), а затем запись в this ( >&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/…
Alex

3
Если сейчас это ненадежно, как насчет обновления ответа на лучшее решение?
zakdances

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

103

При этом по-прежнему используется временный файл, но, по крайней мере, он находится в одной строке:

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

Предоставлено : BASH: добавить текст / строки в файл


4
Что делать -после cat?
макбол

Есть ли способ добавить «текст» без новой строки после?
chishaku

@chishakuecho -n "text"
Sparhawk

3
Предупреждение: если yourfileэто символическая ссылка, это не будет делать то, что вы хотите.
dshepherd

Отличается ли ответ от этого: echo "text"> / tmp / out; cat yourfile >> / 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

Невозможно без временного файла, но вот единственная подсказка

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

Вы можете использовать другие инструменты, такие как ed или perl, чтобы сделать это без временных файлов.


16

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

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

15

Если вам это нужно на компьютерах, которыми вы управляете, установите пакет «moreutils» и используйте «sponge». Тогда вы можете сделать:

cat header myfile | sponge myfile

Это хорошо сработало для меня в сочетании с ответом @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Джесси Халлетт

13

Используя bash heredoc, вы можете избежать необходимости в файле tmp:

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

Это работает, потому что $ (cat myfile) оценивается при оценке сценария bash перед выполнением cat с перенаправлением.


9

предполагая, что файл, который вы хотите отредактировать, - это my.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

Насколько я знаю, это работает только в bash.


почему это работает? Скобки заставляют среднюю кошку выполнять в отдельной оболочке и выгружать весь файл сразу?
Catskul

Я думаю, что <(cat my.txt) создает временный файл где-то в файловой системе. Затем этот файл читается перед изменением исходного файла.
cb0 09

Кто-то однажды показал мне, что можно делать с синтаксисом «<()». Однако я так и не узнал, как называется этот метод, и не смог найти что-то на странице руководства bash. Если кто-то знает об этом больше, дайте мне знать.
cb0 09

1
Не работал для меня Не знаю почему. Я использую GNU bash, версия 3.2.57 (1) -release (x86_64-apple-darwin14), я использую OS X Yosemite. В итоге this is the headerв my.txt осталось две строки . Даже после того, как я обновил Bash, 4.3.42(1)-releaseя получил тот же результат.
Kohányi Róbert

1
Bash не создает временный файл, он создает FIFO (канал), в который записывается команда в процессе substitution ( <(...)) , поэтому нет гарантии, что оно 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

РЕДАКТИРОВАТЬ: это сломано. См. Странное поведение при добавлении к файлу с помощью cat и tee

Решение проблемы перезаписи tee:

cat header main | tee main > /dev/null

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

3
Если опубликованное вами решение не работает (и фактически заполняет жесткие диски пользователей), сделайте одолжение сообществу и удалите свой ответ.
aioobe

1
Можете ли вы указать мне на правило, согласно которому неправильные ответы следует удалять?
Даниэль Велков

3

Тот, который я использую. Это позволяет вам указывать порядок, дополнительные символы и т. Д. Так, как вам нравится:

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

PS: только он не работает, если файлы содержат текст с обратной косой чертой, потому что он интерпретируется как escape-символы


3

В основном для удовольствия / гольфа, но

ex -c '0r myheader|x' myfile

сделает свое дело, и нет конвейеров или перенаправлений. Конечно, vi / ex на самом деле не для неинтерактивного использования, поэтому vi кратковременно вспыхнет.


Лучший рецепт с моей точки зрения, поскольку он использует действующую команду и делает это простым способом.
Диего

2

Почему бы просто не использовать команду ed (как уже было предложено здесь fluffle)?

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

Еще один обходной путь - использовать дескрипторы открытых файлов, как предложил Юрген Хётцель в выводе Redirect из 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 для "без временного файла" для добавления фиксированного текста:

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

Опять же, это зависит от выполнения суб-оболочки - (..) - чтобы кот не отказался иметь один и тот же файл для ввода и вывода.

Примечание: понравилось это решение. Однако на моем Mac исходный файл потерян (думал, что не должен, но это так). Это можно исправить, написав свое решение как: echo "текст для добавления" | кошка - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified


1
Я тоже считаю, что исходный файл потерян. Ubuntu Lucid.
Hedgehog



2

ВНИМАНИЕ: для удовлетворения потребностей OP требуется немного больше работы.

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

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

не выполняется из-за необработанных символов новой строки в сценарии замены, которые должны быть добавлены с символом продолжения строки () и, возможно, за ним следует &, чтобы оболочка и sed были счастливы, как этот ответ SO

sed имеет ограничение по размеру 40 КБ для неглобальных команд поиска-замены (без завершающего символа / g после шаблона), поэтому, вероятно, можно избежать пугающих проблем с переполнением буфера awk, о которых предупреждал аноним.


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

именно то, что я искал, но на самом деле не правильный ответ на вопрос
masterxilo

2

С помощью $ (command) вы можете записать вывод команды в переменную. Поэтому я сделал это тремя командами в одной строке и без временного файла.

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

1

Если у вас есть большой файл (несколько сотен килобайт в моем случае) и доступ к python, это намного быстрее, чем catрешения pipe:

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. Предупреждаю об опасности в ответ.
user137369

На самом деле, вы предупреждали только о процентах в ${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для фиксации сообщений:

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

Пример .gitmessage:

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

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

В связи с этим я обнаружил, что для vim-buffs также хорошо иметь:

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

0

переменные, 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

Думаю, это самый чистый вариант ed:

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

ИМХО нет (и никогда не будет) оболочки, которая работала бы последовательно и надежно независимо от размеров двух файлов myheaderи файлов myfile. Причина в том, что если вы хотите сделать это, не возвращаясь к временному файлу (и не позволяя оболочке молча возвращаться к временному файлу, например, через такие конструкции, как exec 3<>myfile, трубопровод кtee и т. Д.

«Настоящее» решение, которое вы ищете, должно возиться с файловой системой, поэтому оно недоступно в пользовательском пространстве и будет зависеть от платформы: вы просите изменить указатель файловой системы, который используется, myfileна текущее значение указателя файловой системы. for myheaderи замените в файловой системе EOFof myheaderна связанную ссылку на текущий адрес файловой системы, на который указывает myfile. Это нетривиально и, очевидно, не может быть сделано не суперпользователем, и, вероятно, не суперпользователем ... Играйте с inode и т. Д.

Однако вы можете более или менее подделать это с помощью петлевых устройств. См., Например, эту тему SO .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.