Як замінити часові позначки епохи у файлі іншими форматами?


10

У мене є файл, що містить дати епохи, які мені потрібно перетворити на людину, читабельну. Я вже знаю, як зробити перетворення дати, наприклад:

[server01 ~]$ date -d@1472200700
Fri 26 Aug 09:38:20 BST 2016

..але я намагаюся зрозуміти, як sedпройти по файлу та конвертувати всі записи. Формат файлу виглядає приблизно так:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

1
Для подальшого ознайомлення (якщо припустити, що це файл історії Bash; він схожий на один), зверніться до HISTTIMEFORMATзмінної оболонки, щоб контролювати формат під час написання.
Toby Speight

@Таким чином значення HISTTIMEFORMAT використовується під час відображення (для stdout), але при написанні HISTFILE має значення лише його статус (встановлений на що-небудь навіть нульове проти не встановленого).
dave_thompson_085

Дякую @dave, я цього не знав (не будучи самим користувачем історії).
Toby Speight

date -dне переносимо, щоб сказати Solaris ... Я припускаю, що це в системі з переважно інструментами GNU? (GNU AWK / Perl, як правило, є більш портативними методами боротьби з перетвореннями дат). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimeздається, не портативний ...)
Герт ван ден Берг

Відповіді:


6

Якщо припустити послідовний формат файлу, bashви можете прочитати файл за рядком, перевірити, чи він у заданому форматі, а потім виконати конверсію:

while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && \
      date -d@"${BASH_REMATCH[1]}"; done <file.txt

BASH_REMATCH- це масив, першим елементом якого є перша захоплена група у збігу Regex =~, в даному випадку епоха.


Якщо ви хочете зберегти структуру файлу:

while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' \
   "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt

це виведе змінений вміст у STDOUT, щоб зберегти його у файлі, наприклад out.txt:

while ...; do ...; done >out.txt

Тепер за бажанням ви можете замінити оригінальний файл:

mv out.txt file.txt

Приклад:

$ cat file.txt
#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

$ while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && date -d@"${BASH_REMATCH[1]}"; done <file.txt
Wed Aug 24 20:09:55 BDT 2016
Wed Aug 24 20:11:46 BDT 2016
Wed Aug 24 20:13:58 BDT 2016

$ while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt
#Wed Aug 24 20:09:55 BDT 2016
ll /data/holding/email
#Wed Aug 24 20:11:46 BDT 2016
cat /etc/rsyslog.conf
#Wed Aug 24 20:13:58 BDT 2016
ll /data/holding/web

Приємно .... що друкує перетворену дату на екран, тепер як я можу отримати цю команду для заміни записів у файлі?
машиніст

@machinist Перевір мої зміни ..
heemayl

1
Якщо ви використовуєте останню версію bash, printfможе зробити сам перетворення: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
чепнер

14

Хоча в GNU це можливо sed:

sed -E 's/^#([0-9]+).*$/date -d @\1/e'

Це було б дуже неефективно (і легко запровадити довільні вразливості введення команд 1 ), оскільки це означатиме виконання однієї оболонки та однієї dateкоманди для кожного #xxxxрядка, практично так само погано, як і while readцикл оболонки . Тут було б краще використовувати такі речі , як perlі gawk, тобто утиліти для обробки тексту , які мають можливості перетворення дати вбудовані:

perl  -MPOSIX -pe 's/^#(\d+).*/ctime $1/se'

Або:

gawk '/^#/{$0 = strftime("%c", substr($0, 2))};1'

1 Якби ми писали ^#([0-9]).*замість ^#([0-9]).*$(як я це робив у попередній версії цієї відповіді), то в багатобайтових локалях, таких як UTF-8 (норма сьогодні), з введенням типу #1472047795<0x80>;reboot, де <0x80>це значення байта 0x80, яке не утворює дійсного символу, ця sкоманда закінчилася б, date -d@1472047795<0x80>; rebootнаприклад,. Хоча з додатковими $, ці рядки не будуть замінені. Альтернативним підходом було б: s/^#([0-9])/date -d @\1 #/eтобто залишити частину після #xxxдати як коментар оболонки


1
Що з використанням лише одного екземпляра,date -f щоб зробити всі перетворення потоковим способом?
Digital Trauma

Команда perl, здається, додає новий рядок після ctime $ 1, і я не можу знайти жодного способу її видалити.
Алекс Харві

1
@Alex. Правильно. Див. Редагування. Додавання sпрапора робить таким чином, що .*також включає новий рядок на вході. Ви також можете використовувати strftime "%c", localtime $1.
Стефан Шазелас

@ StéphaneChazelas велике спасибі Це чудова відповідь.
Алекс Харві

3

Усі інші відповіді породжують новий dateпроцес для кожної дати епохи, який потрібно перетворити. Це може потенційно підвищити ефективність роботи, якщо ваш вклад великий.

Однак дата GNU має зручну -fопцію, яка дозволяє одному екземпляру процесу dateпостійно читати дати введення без необхідності нового вила. Таким чином, ми можемо використовувати sed, pasteі dateтаким чином, що кожен отримує породження лише один раз (2x для sed), незалежно від того, наскільки великий вхід:

$ paste -d '\n' <( sed '2~2d;y/#/@/' epoch.txt | date -f - ) <( sed '1~2d' epoch.txt )
Wed Aug 24 07:09:55 PDT 2016
ll /data/holding/email
Wed Aug 24 07:11:46 PDT 2016
cat /etc/rsyslog.conf
Wed Aug 24 07:13:58 PDT 2016
ll /data/holding/web
$ 
  • Дві sedкоманди відповідно видаляють парні та непарні рядки вводу; перший і замінює #з , @щоб дати правильний формат тимчасової мітки епохи.
  • Перший sedвисновок потім прокладається, до date -fякого проводиться необхідне перетворення дати, для кожного введеного рядка вводу.
  • Ці два потоки потім переплітаються в єдиний необхідний вихід, використовуючи paste. Ці <( )конструкції є Баш процес заміни , які ефективно Trick Пасти, думаючи , що читає з заданих імен файлів , коли це насправді читає висновок надходить з команди зсередини. -d '\n'повідомляє pasteвідокремлювати непарні і парні рядки з новим рядком. Ви можете змінити (або видалити) це, якщо, наприклад, хочете позначити часову позначку в тому ж рядку, що й інший текст.

Зауважте, що в цій команді є кілька GNUizmi та башизмів. Це не сумісно з Posix, і не слід очікувати, що воно буде портативним поза межами світу GNU / Linux. Наприклад, date -fробиться щось інше у dateваріанті OSXes BSD .


date -d(з питання) також не переноситься ... (На FreeBSD він спробує возитися з налаштуваннями DST, на Solaris він дасть помилку ...) Питання не вказує ОС, хоча ...
Герт ван ден Берг

@GertvandenBerg так, це стосується останнього пункту цієї відповіді.
Цифрова травма

Я маю на увазі, що у зразка запитувача також є проблеми з портативністю ... (Мабуть, вони повинні позначити ОС ...)
Герт ван ден Берг

1

Якщо припустити, що формат дати у вашій публікації - це те, що ви хочете, наступний регекс повинен відповідати вашим потребам.

sed -E 's/\#(1[0-9]{9})(.*)/echo \1 $(date -d @\1)/e' log.file

Пам’ятайте про те, що це замінить лише одну епоху на рядок.


З цією командою я отримую таку помилку: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
машиніст

1
Моя помилка, редагував пост.
Хетчлок

0

за допомогою sed:

sed -r 's/\#([0-9]*)/echo $(date -d @\1)/eg' test.txt

вихід:

ر أغس 24 16:09:55 EET 2016
ll /data/holding/email
ر أغس 24 16:11:46 EET 2016
cat /etc/rsyslog.conf
ر أغس 24 16:13:58 EET 2016
ll /data/holding/web

як моя мова мови арабська :)


0

Моє рішення, як це зробити в конвеєрі

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.