#!/bin/sh # Edit a file without changing its timestamps. # Both access and modification times are maintained, # or optionally those timestamps are set to a specific epoch. # License: LGPLv2 # Author: # http://www.pixelbeat.org/ # Notes: # $EDITOR if set, must not fork at startup, so if you # want to use gvim for example, ensure EDITOR="gvim -f". # Currently linux can read timestamps with nanosecond resolution # but only set a specific timestamp with microsecond resolution # (if the filesystem even supports that). # Changes: # V1.0, 31 May 2006, Initial release # V1.1, 13 Jul 2007, Allow specifying epoch # Remove the use of temp files # V1.2, 24 Jul 2007, Fallback to second resolution for touch implementations # that don't handle nanoseconds in the date string. # Warn if nanosecond resolution is lost due to limitations # in `touch`, the libc/kernel interface or the filesystem. # V1.3, 05 Feb 2009, Don't allow to specify epoch with leading + or - # as it's common to call vim like: vim file +line_num etc. # Only allow 2 parameters rather than ignoring extra ones. global file := $1 global epoch := $2 if test ! -f $file || test $Argc -ne 1 -a $Argc -ne 2 { echo "Usage: $[basename $0] file [epoch]" > !2 exit 1 } if test ! $epoch { global epoch := $[date --reference="$file" +%s.%N] || exit 1 } else { if echo $epoch | grep -Eq "^[\+-]" { echo "Epochs with leading +/- are ambiguous with editor options" > !2 exit 1 } if ! date --date="1970-01-01 UTC $epoch seconds" >/dev/null !2 > !1 { echo "Invalid epoch specified [$epoch]" > !2 exit 1 } } if echo $epoch | grep -Fq "." { #valid nanosecond format global seconds := $[echo $epoch | cut -d. -f1] if echo $epoch | grep -Eq "\.0+$" { #strip redundant nanoseconds global epoch := $seconds #since touch may not support nanosecond format } else { global checkns := '"true'" #need to check nanosecond portion later } } $(EDITOR:-vim) $file test ! $seconds && global err := '"/dev/tty'" || global err := '"/dev/null'" touch $file --date="1970-01-01 UTC $epoch seconds" !2 >$err if test $Status -ne 0 -a $seconds { #maybe touch doesn't support nanoseconds touch $file --date="1970-01-01 UTC $seconds seconds" && echo "Warning: sub second portion of timestamp ignored" > !2 } else { if test $checkns { global new_ts := $[date --reference="$file" +%s.%N] global diff := $[echo "(($epoch-$new_ts)*10^9)/1" | bc] if test $diff -ne 0 { echo "Warning: timestamp set $(diff)ns backwards" > !2 } } } #Hmm could have option to inc time by 1 second #so that updates would be noticed as normal #but the relative ordering of a file in time #in relation to other files would probably be unchanged (CommandList children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:file) op: Equal rhs: {(DQ ($ VSub_Number "$1"))} spids: [75] ) ] spids: [75] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:epoch) op: Equal rhs: {(DQ ($ VSub_Number "$2"))} spids: [80] ) ] spids: [80] ) (If arms: [ (if_arm cond: [ (Sentence child: (AndOr children: [ (C {(Lit_Other "[")} {(KW_Bang "!")} {(-f)} {(DQ ($ VSub_Name "$file"))} {(Lit_Other "]")} ) (C {(Lit_Other "[")} {($ VSub_Pound "$#")} {(-ne)} {(1)} {(-a)} {($ VSub_Pound "$#")} {(-ne)} {(2)} {(Lit_Other "]")} ) ] op_id: Op_DPipe ) terminator: ) ] action: [ (SimpleCommand words: [ {(echo)} { (DQ ("Usage: ") (CommandSubPart command_list: (CommandList children:[(C {(basename)} {($ VSub_Number "$0")})]) left_token: spids: [128 132] ) (" file [epoch]") ) } ] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[136])] ) (C {(exit)} {(1)}) ] spids: [-1 121] ) ] spids: [-1 144] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$epoch"))} {(Lit_Other "]")}) terminator: ) ] action: [ (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:epoch) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (C {(date)} {(--reference) (Lit_Other "=") (DQ ($ VSub_Name "$file"))} {(Lit_Other "+") (Lit_Other "%") (s.) (Lit_Other "%") (N)} ) ] ) left_token: spids: [164 178] ) } spids: [163] ) ] spids: [163] ) (C {(exit)} {(1)}) ] op_id: Op_DPipe ) ] spids: [-1 160] ) ] else_action: [ (If arms: [ (if_arm cond: [ (Sentence child: (Pipeline children: [ (C {(echo)} {(DQ ($ VSub_Name "$epoch"))}) (C {(grep)} {(-Eq)} {(DQ ("^[") (EscapedLiteralPart token:) ("-]"))} ) ] negated: False ) terminator: ) ] action: [ (SimpleCommand words: [{(echo)} {(DQ ("Epochs with leading +/- are ambiguous with editor options"))}] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[219])] ) (C {(exit)} {(1)}) ] spids: [-1 210] ) ] spids: [-1 228] ) (If arms: [ (if_arm cond: [ (Sentence child: (Pipeline children: [ (SimpleCommand words: [ {(date)} {(--date) (Lit_Other "=") (DQ ("1970-01-01 UTC ") ($ VSub_Name "$epoch") (" seconds")) } ] redirects: [ (Redir op_id: Redir_Great fd: -1 arg_word: {(/dev/null)} spids: [245] ) (Redir op_id: Redir_GreatAnd fd: 2 arg_word: {(1)} spids: [248] ) ] ) ] negated: True ) terminator: ) ] action: [ (SimpleCommand words: [{(echo)} {(DQ ("Invalid epoch specified [") ($ VSub_Name "$epoch") ("]"))}] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[263])] ) (C {(exit)} {(1)}) ] spids: [-1 252] ) ] spids: [-1 272] ) ] spids: [186 274] ) (If arms: [ (if_arm cond: [ (Sentence child: (Pipeline children: [(C {(echo)} {(DQ ($ VSub_Name "$epoch"))}) (C {(grep)} {(-Fq)} {(DQ (.))})] negated: False ) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:seconds) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {($ VSub_Name "$epoch")}) (C {(cut)} {(-d.)} {(-f1)}) ] negated: False ) ] ) left_token: spids: [303 315] ) } spids: [302] ) ] spids: [302] ) (If arms: [ (if_arm cond: [ (Sentence child: (Pipeline children: [ (C {(echo)} {($ VSub_Name "$epoch")}) (C {(grep)} {(-Eq)} { (DQ (EscapedLiteralPart token:) ("0+") (Lit_Other "$") ) } ) ] negated: False ) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:epoch) op: Equal rhs: {($ VSub_Name "$seconds")} spids: [343] ) ] spids: [343] ) ] spids: [-1 337] ) ] else_action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:checkns) op: Equal rhs: {(DQ (true))} spids: [353] ) ] spids: [353] ) ] spids: [350 362] ) ] spids: [-1 296] ) ] spids: [-1 364] ) (C { (BracedVarSub token: suffix_op: (StringUnary op_id:VTest_ColonHyphen arg_word:{(vim)}) spids: [366 370] ) } {(DQ ($ VSub_Name "$file"))} ) (AndOr children: [ (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$seconds"))} {(Lit_Other "]")}) (AndOr children: [ (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:err) op:Equal rhs:{(DQ (/dev/tty))} spids:[388])] spids: [388] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:err) op: Equal rhs: {(DQ (/dev/null))} spids: [395] ) ] spids: [395] ) ] op_id: Op_DPipe ) ] op_id: Op_DAmp ) (SimpleCommand words: [ {(touch)} {(DQ ($ VSub_Name "$file"))} {(--date) (Lit_Other "=") (DQ ("1970-01-01 UTC ") ($ VSub_Name "$epoch") (" seconds"))} ] redirects: [(Redir op_id:Redir_Great fd:2 arg_word:{($ VSub_Name "$err")} spids:[414])] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {($ VSub_QMark "$?")} {(-ne)} {(0)} {(-a)} {(DQ ($ VSub_Name "$seconds"))} {(Lit_Other "]")} ) terminator: ) ] action: [ (AndOr children: [ (C {(touch)} {(DQ ($ VSub_Name "$file"))} {(--date) (Lit_Other "=") (DQ ("1970-01-01 UTC ") ($ VSub_Name "$seconds") (" seconds")) } ) (SimpleCommand words: [{(echo)} {(DQ ("Warning: sub second portion of timestamp ignored"))}] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[465])] ) ] op_id: Op_DAmp ) ] spids: [-1 436] ) ] else_action: [ (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$checkns"))} {(Lit_Other "]")}) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:new_ts) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (C {(date)} {(--reference) (Lit_Other "=") (DQ ($ VSub_Name "$file"))} {(Lit_Other "+") (Lit_Other "%") (s.) (Lit_Other "%") (N)} ) ] ) left_token: spids: [486 500] ) } spids: [485] ) ] spids: [485] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:diff) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} { (DQ ("((") ($ VSub_Name "$epoch") (-) ($ VSub_Name "$new_ts") (")*10^9)/1") ) } ) (C {(bc)}) ] negated: False ) ] ) left_token: spids: [504 518] ) } spids: [503] ) ] spids: [503] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {($ VSub_Name "$diff")} {(-ne)} {(0)} {(Lit_Other "]")}) terminator: ) ] action: [ (SimpleCommand words: [ {(echo)} {(DQ ("Warning: timestamp set ") (${ VSub_Name diff) ("ns backwards"))} ] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[547])] ) ] spids: [-1 534] ) ] spids: [-1 551] ) ] spids: [-1 482] ) ] spids: [-1 554] ) ] spids: [468 556] ) ] )