Возможно, это комплексное решение.
Я ищу простой оператор типа " → ", но для добавления.
Я боюсь, что этого не существует. Мне нужно сделать что-то вроде
mv myfile tmp cat myheader tmp > myfile
Что-нибудь умнее?
hack ниже был быстрый ответ от манжеты, который работал и получил много upvotes. Затем, когда вопрос стал более популярным, и больше времени прошло, возмущенные люди начали сообщать, что он сорта работал, но странные вещи могут произойти, или это просто не сработало, так что это было яростно приостановлено на время. Такая забава.
Решение использует точную реализацию файловых дескрипторов в вашей системе и, поскольку реализация существенно различается между nixes, успех полностью зависящий от системы, окончательно не переносимый, и на него нельзя полагаться ни на что, даже смутно важное.
Теперь со всем, что было в ответе:
Создание другого файлового дескриптора для файла (exec 3<> yourfile
), поэтому запись на него (>&3
), похоже, преодолевает проблему чтения/записи в одном и том же файле. Работает для меня на 600K файлах с awk. Однако попытка использовать тот же трюк с использованием "cat" не удалась.
Передача preendage как переменной в awk (-v TEXT="$text"
) преодолевает литеральную проблему с кавычками, которая предотвращает выполнение этого трюка с помощью команды sed.
#!/bin/bash
text="Hello world
What up?"
exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3
Это все еще использует временный файл, но по крайней мере он находится в одной строке:
echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile
-
после cat
?
echo '0a
your text here
.
w' | ed some_file
ed - это стандартный редактор! http://www.gnu.org/fun/jokes/ed.msg.html
0r header.file
текста для вставки: 0r header.file
John Mee: ваш метод не гарантированно работает, и, вероятно, он потерпит неудачу, если вы добавите более 4096 байт материала (по крайней мере, то, что происходит с gnu awk, но я полагаю, что другие реализации будут иметь схожие ограничения). В этом случае он не только потерпит неудачу, но и войдет в бесконечный цикл, где он будет читать свой собственный вывод, тем самым увеличив файл до тех пор, пока не будет заполнено все свободное пространство.
Попробуйте сами:
exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3
(предупреждение: убить его через некоторое время или заполнить файловую систему)
Кроме того, очень опасно редактировать файлы таким образом, и это очень плохой совет, как будто что-то происходит, когда файл редактируется (сбой, полный диск), вы почти гарантированно остаетесь с файлом в непоследовательной состояние.
Возможно, стоит отметить, что часто это хорошая идея для безопасного создания временного файла с помощью утилиты, такой как mktemp, по крайней мере, если script будет когда-либо выполняются с привилегиями root. Например, вы можете сделать следующее (снова в bash):
(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )
Невозможно без временного файла, но здесь идет oneliner
{ echo foo; cat oldfile; } > newfile && mv newfile oldfile
Вы можете использовать другие инструменты, такие как ed или perl, чтобы сделать это без временных файлов.
Если вам это нужно на компьютерах, которыми вы управляете, установите пакет "moreutils" и используйте "губку". Затем вы можете сделать:
cat header myfile | sponge myfile
РЕДАКТОР: Это нарушено. Смотрите странное поведение при добавлении файла с кошкой и телом
Обходной путь к проблеме перезаписи заключается в использовании tee
:
cat header main | tee main > /dev/null
Когда вы начинаете пытаться делать вещи, которые становятся трудными в shell- script, я настоятельно рекомендую изучить переписывание script на "правильном" языке сценариев (Python/Perl/Ruby/etc)
Что касается добавления строки к файлу, это невозможно сделать с помощью конвейера, так как когда вы делаете что-либо вроде cat blah.txt | grep something > blah.txt
, он непреднамеренно заполняет файл. Существует небольшая команда утилиты sponge
, которую вы можете установить (вы делаете cat blah.txt | grep something | sponge blah.txt
и буферизуете содержимое файла, а затем записываете его в файл). Он похож на временный файл, но вам не нужно делать это явно. но я бы сказал, что это "худшее" требование, чем, скажем, Perl.
Возможно, есть способ сделать это с помощью awk или аналогичного, но если вам нужно использовать shell- script, я думаю, что временный файл является самым простым (/only?) способом.
Как предлагает Даниэль Велков, используйте тройник.
Для меня это простое интеллектуальное решение:
{ echo foo; cat bar; } | tee bar > /dev/null
Предполагая, что файл, который вы хотите изменить, - 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.
Используя bash heredoc, вы можете избежать необходимости в файле tmp:
cat <<-EOF > myfile
$(echo this is prepended)
$(cat myfile)
EOF
Это работает, потому что $(cat myfile) оценивается, когда оценивается bash script, прежде чем выполняется кошка с перенаправлением.
В основном для гольфа с потерей/раковиной, но
ex -c '0r myheader|x' myfile
сделает трюк, и нет конвейеров или перенаправления. Конечно, vi/ex не предназначен для неинтерактивного использования, поэтому vi будет кратковременно мигать.
Тот, который я использую. Это позволяет указать порядок, дополнительные символы и т.д. Так, как вам нравится:
echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt
P.S: только он не работает, если файлы содержат текст с обратным слэшем, потому что он интерпретируется как escape-символы
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile
Почему бы просто не использовать команду ed (как уже было предложено пудрой здесь)?
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
Все это можно было бы поместить в одну строку, конечно.
ПРЕДУПРЕЖДЕНИЕ: для этого требуется немного больше работы для удовлетворения потребностей OP.
Должен быть способ сделать подход sed by @shixilun работать, несмотря на его опасения. Для чтения пробела при чтении файла в строку замены sed должна быть команда bash (например, заменить символы новой строки на "\n". Команды оболочки vis
и cat
могут обрабатывать непечатаемые символы, но не пробелы, поэтому это не решит проблему OP:
sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt
не удается из-за исходных строк в замене script, которые должны быть добавлены с символом продолжения строки() и, возможно, сопровождаться символом &, чтобы поддерживать оболочку и sed счастливыми, например этот ответ SO
sed
имеет ограничение по размеру 40K для неглобальных команд замены на поиск (без трейлинга /g после шаблона), поэтому вероятно избежит проблем с переполнением страшного буфера awk, о котором предупреждали анонимные пользователи.
Вариант решения cb0 для "no temp file" для добавления фиксированного текста:
echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified )
Опять же, это зависит от выполнения суб-оболочки - (..) - чтобы кошка отказалась иметь один и тот же файл для ввода и вывода.
Примечание: понравилось это решение. Однако на моем Mac исходный файл потерян (подумал, что он не должен, но он это делает). Это можно исправить, написав свое решение как: echo "text to prepend" | cat - file_to_be_modified | cat > tmp_file; mv tmp_file file_to_be_modified
Вы можете использовать командную строку 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 разрешит оценку кода в подстановке.
Решение с 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}"
Но в этом случае вы должны быть уверены, что в любом месте %
где-нибудь, включая содержимое целевого файла, не будет, поскольку это можно интерпретировать и испортить ваши результаты.
echo
кажется более безопасным вариантом. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Если у вас есть большой файл (несколько сотен килобайт в моем случае) и доступ к python, это намного быстрее, чем cat
решения для труб:
python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'
С помощью $(команда) вы можете записать вывод команды в переменную. Поэтому я сделал это в трех командах в одной строке и без временного файла.
originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile
Быстро и грязно, буферизуйте все в памяти с помощью python:
$ echo two > file
$ echo one | python -c "import sys; f=open(sys.argv[1]).read(); open(sys.argv[1],'w').write(sys.stdin.read()+f)" file
$ cat file
one
two
$ # or creating a shortcut...
$ alias prepend='python -c "import sys; f=open(sys.argv[1]).read(); open(sys.argv[1],\"w\").write(sys.stdin.read()+f)"'
$ echo zero | prepend file
$ cat file
zero
one
two
IMHO нет решения оболочки (и никогда не будет такого), который будет работать последовательно и надежно независимо от размеров двух файлов myheader
и myfile
. Причина в том, что если вы хотите сделать это, не повторяясь во временном файле (и не позволяя оболочке тихо вернуться к временному файлу, например, через конструкции, такие как exec 3<>myfile
, piping to tee
и т.д.
"Реальное" решение, которое вы ищете, должно возиться с файловой системой, и поэтому оно недоступно в пользовательском пространстве и будет зависящим от платформы: вы просите изменить указатель файловой системы, используемый myfile
на текущее значение указателя файловой системы для myheader
и замените в файловой системе EOF
на myheader
цепочкой ссылку на текущий адрес файловой системы, указанный myfile
. Это не тривиально и, очевидно, не может быть сделано не суперпользователем, а, вероятно, не суперпользователем... Играйте с inodes и т.д.
Вы можете более или менее подделать это, используя устройства loop. См. Например этот поток SO.
Я думаю, что это самая чистая вариация 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
Вот что я обнаружил:
echo -e "header \n$(cat file)" >file
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
Мне нравится @fluffle ed подход. В конце концов, любые команды командной строки переключаются на скриптовые команды редактора, по сути, здесь одно и то же; не видя, что сценаристское решение редактора "чистота" меньше или меньше.
Здесь мой однострочный файл добавлен к .git/hooks/prepare-commit-msg
для добавления файла in-repo .gitmessage
для фиксации сообщений:
echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"
Пример .gitmessage
:
# Commit message formatting samples:
# runlevels: boot +consolekit -zfs-fuse
#
Я делаю 1r
вместо 0r
, потому что это оставит пустую строку с готовой записью поверх файла из исходного шаблона. Не помещайте пустую строку поверх вашего .gitmessage
, тогда вы получите две пустые строки. -s
подавляет вывод диагностической информации ed.
В связи с этим я обнаружил, что для vim-buffs также хорошо иметь:
[core]
editor = vim -c ':normal gg'
sed -i -e "1s/^/new first line\n/" old_file.txt
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file
где "my_file" - это файл, который добавляет "my_string" к.
Ба! Никто не хотел упоминать о tac.
endor@grid ~ $ tac --help
Usage: tac [OPTION]... [FILE]...
Write each FILE to standard output, last line first.
With no FILE, or when FILE is -, read standard input.
Mandatory arguments to long options are mandatory for short options too.
-b, --before attach the separator before instead of after
-r, --regex interpret the separator as a regular expression
-s, --separator=STRING use STRING as the separator instead of newline
--help display this help and exit
--version output version information and exit
Report tac bugs to [email protected]
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
Report tac translation bugs to <http://translationproject.org/team/>
{ tac myfile; tac header; } | tac > newfile && mv newfile myfile
Если вы создаете сценарий в BASH, на самом деле, вы можете просто выпустить:
cat - yourfile /tmp/out && mv /tmp/out yourfile
Что на самом деле в сложном примере вы сами разместили в своем собственном вопросе.
cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile
, однако это довольно cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile
отличается от моего ответа, так как это должен быть его собственный ответ.
mktemp
? Вы всегда можете очистить временный файл потом ...