How to restore the modification time of a file after changing it? - unix

I am modifying some files with the help of a script in Unix. I don't want the modification times of the files to be changed. I used the touch command but no use. Is there any other way?
I want the previous modification time of the file. Is it possible?

Touch is the way to go. Was your syntax correct?
[01:35:42 root#~]# touch -t 201107262235.34 foo
[01:35:49 root#~]# stat foo
File: `foo'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: ca20h/51744d Inode: 642445 Links: 1
Access: (0600/-rw-------) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2011-07-26 22:35:34.000000000 -0400
Modify: 2011-07-26 22:35:34.000000000 -0400
Change: 2011-07-27 01:35:49.000000000 -0400
[01:35:50 root#~]#

backup:
# savedate=$(stat -c %Y filename.ext)
restore:
# touch -d #${savedate} filename.ext

Capture the modification time before changing the file:
oldFileTime=`find theFileThatIsBeingChanged -maxdepth 0 -printf "%Ty%Tm%Td%TH%TM.%.2TS"`
Make your modifications, then use touch to reset the time:
touch -t "$oldFileTime" theFileThatIsBeingChanged

Related

Is there any utility in Solaris/AIX to package a shell Script?

I have a Bash shell script which takes three input files as an argument. I would like to package them all, so I can place that package on any UNIX machines and run it.
There is an old and ancient technique to include a tar file in a executable well known to the grey haired admins ;)
At first create a script ... put it into a file named script
#!/bin/bash
TAR_STARTS=`awk '/^__TARMAN BEGINS__/ { print NR + 1; exit 0; }' $0`
NAME_OF_SCRIPT=`pwd`/$0
tail +$TAR_STARTS $NAME_OF_SCRIPT | gunzip -c | tar -xvf -
# Insert commands to execute after untaring here .... with relative
# pathname (e.g. if there is directory "bin" with a file "executable"
# then you should insert a line bin/executable
exit
__TARMAN BEGINS__
No newline after the last __
Of course this script is derived from somewhere in the internet. It's not mine. I just cannot remember where for proper kudos.
Then create your tarfile and put it at the end of the file. This is the reason why it's nescessary that there is no newline after the __
$ cat script test.tar.gz > selfexploding.sh
Now you can just try it
$ bash ./selfexploding.sh
tar: blocksize = 9
x testtar, 0 bytes, 0 tape blocks
x testtar/test2, 1024 bytes, 2 tape blocks
x testtar/test1, 1024 bytes, 2 tape blocks
You could of course put the name of a script before the exit, that you create by unpack ... of course path must be relative to the pwd of the execution. Don't know if this works with AIX. At least with Solaris 11.3 it works. But as it only uses standard command. It should work everywhere. Besides of this you could of course create native packages for Solaris and AIX.

Send multiple outputs to file without overriding

I'm trying to send the output of two commands in UNIX to a file called "log.txt"
Right now I've been trying:
# date ; quota -v myName > log.txt
The intent is to have my log.txt file look like:
Mon Sep 11 14:13:34 PDT 2006
Disk Quota for ....
...
...
Where the first line represents the date command and the rest represent the quota command.
Is there a way to send the outputs of both of these commands to the same log.txt file without overriding each other?
Use parentheses to group your commands for the redirect of standard output.
(date ; quota -v myName) > log.txt
For example:
# (date; echo "hi") > foo
# cat foo
Sat Feb 9 23:09:15 PST 2013
hi
Louis's answer of >> works better if you want to have many commands in a big script. The first command should use > so that it truncates any existing contents of the file. All the other commands use >> to append to the file.
You want to append, use the >> So something like:
date >> log.txt && quota -v myName >> log.txt

Using sed to change /etc/fstab

I would like to change /etc/fstab inside a script. I want to add the aclattribute to the root partition.
One fstabline entry looks like this:
UUID=730aee20-52b7-4920-75cd-d0d995ef2445 / ext3 errors=remount-ro 0 1
I want to change it to:
UUID=730aee20-52b7-4920-75cd-d0d995ef2445 / ext3 acl,errors=remount-ro 0 1
I thought:
1. Search line with root partition /
2. insert acl after /
How can I do that with sed?
Who needs some 3rd party tool when we all have awk?
awk '$2~"^/$"{$4="acl,"$4}1' OFS="\t" /etc/fstab
Example Output
$ awk '$2~"^/$"{$4="acl,"$4}1' OFS="\t" /etc/fstab
/dev/sda2 swap swap defaults 0 0
/dev/sda5 / ext4 acl,defaults 1 1
/dev/sda1 /boot ext4 defaults 1 2
/dev/sda6 /home ext4 defaults 1 2
/dev/sdb1 /backup ext4 defaults 1 2
#/dev/cdrom /mnt/cdrom auto noauto,owner,ro 0 0
/dev/fd0 /mnt/floppy auto noauto,owner 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
proc /proc proc defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
Explanation
$2~"^/$" Search the 2nd field $2 to see if it matches a forward slash by itself ^/$
{$4="acl,"$4} If we see a match, prepend acl to the beginning of the 4th field $4
}1 This is an awk shortcut which is equivalent to print $0, i.e. print the whole line (including any alterations we may have made)
OFS="\t" Set the Output Field Separator OFS to a tab \t. The default is space
/etc/fstab The file we want to use as input
I guess the best way would be to use augeas, which is a great tool. With augeas you can parse configuration files automatically, append/delete/update some field with XPATH, which is used to navigate through the XML.
You can find all the supported conifiguration files that augeas can correctly parse in the following directory:
/usr/share/augeas/lenses/dist/
One of the files is fstab.aug, which is the one you want. You don't actually need to do anything with that files, it's just there to let you know that augeas can do what you want :).
# ls /usr/share/augeas/lenses/dist/fstab.aug
/usr/share/augeas/lenses/dist/fstab.aug
You can also look here for more detailed example obout changing /etc/fstab through augeas.
Augeas Example
I guess you can use something like the following:
# augtool
augtool> set /files/etc/fstab/1[file='/']/opt[1] "acl"
augtool> set /files/etc/fstab/1[file='/']/opt[2] "errors=remount-ro"
augtool> print /files/etc/fstab/1
/files/etc/fstab/1
/files/etc/fstab/1/spec = "/dev/mapper/system"
/files/etc/fstab/1/file = "/"
/files/etc/fstab/1/vfstype = "ext3"
/files/etc/fstab/1/opt[1] = "acl"
/files/etc/fstab/1/opt[2] = "errors=remount-ro"
/files/etc/fstab/1/dump = "0"
/files/etc/fstab/1/passno = "1"
augtool> save
If you want to use augeas in a shell script you can just preced the above commands with augtool keyword, so if you want to change the first 'opt' to 'acl' you would do:
augtool set /files/etc/fstab/1[file='/']/opt[1] "acl"
augtool set /files/etc/fstab/1[file='/']/opt[2] "errors=remount-ro"
augtool save
Look at the original augeas page for more: Augeas Homepage
Added value to Wes's one - \w instead of [a-zA-Z0-9], \s instead of space. And it actually works :)
sed -e 's:\(.*\)\(/\s*\w*\s*\)\(.*\):\1\2acl,\3:' /etc/fstab
Well, I won't debate whether sed is the right tool for the job or not (I'm not sure it is), but:
sed 's#( / +[a-zA-Z0-9]+ +)#$1acl,#'
Which should find the / mount, and add acl just before the 2nd field after the mount point.
This might work for you:
sed 's|^\S*\s\+/\s\+\S*\s\+|&acl,|' /etc/fstab
the following works for me (looking for the "/" (root) mount point and adding "acl," in front of "defaults"):
sed -e 's:\(.*\)\(\s/\s\s*\)\(\w*\s*\)\(\w*\s*\)\(.*\):\1\2\3acl,\4\5:' /etc/fstab
on the contents of my fstab:
/dev/VolGroup00/LogVol00 / ext3 defaults 1 1
LABEL=/boot /boot ext3 defaults 1 2
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
/dev/VolGroup00/LogVol01 swap swap defaults 0 0
It groups the contents into five fields (volume, mount point, file-system type, options, everything else) split by whitespace.

How to remove an entry from the history in ZSH

Let's say I ran a command using a zsh
echo "mysecret" > file
I can easily print the history including the entry numbers using the command fc -l:
1 echo "mysecret" >| file
But how can I easily delete an entry from the history?
I cannot find a corresponding paragraph in man zshbuiltins.
*BSD/Darwin (macOS):
LC_ALL=C sed -i '' '/porn/d' $HISTFILE
Linux (GNU sed):
LC_ALL=C sed -i '/porn/d' $HISTFILE
This will remove all lines matching "porn" from your $HISTFILE.
With setopt HIST_IGNORE_SPACE, you can prepend the above command with a space character to prevent it from being written to $HISTFILE.
As Tim pointed out in his comment below, the prefix LC_ALL=C prevents 'illegal byte sequence' failure.
I don't know if there is some elegant method for doing this, but in similar situations I have logged out (allowing zsh to empty its buffer and write my history to file), then logged in, and finally manually edited ~/.zsh_history, deleting the "dangerous" line.
If you use the HIST_IGNORE_SPACE option in zsh you can prepend commands with a space " " and they will not be remembered in the history file. If you have secret commands you commonly use you can do something along the lines of: alias hiddencommand=' hiddencommand'.
If you only want to make an occasional deletion, I think that it's easier to manually edit your .zsh_history.
In a zsh terminal:
Close the terminal session with the command to delete.
open a new session,
open ~/.zsh_history with a text editor (pico, Emacs, vim...),
delete the faulty lines,
close the editor, close the terminal session and open a new one,
enter history and the unwanted history item will be gone.
(Make sure the editor hasn't backed up the previous .zsh_history instance.)
(Solution based on https://til.hashrocket.com/posts/zn87awopb4-delete-a-command-from-zsh-history-)
This function will remove any one line you want from your Zsh history, no questions asked:
# Accepts one history line number as argument.
# Use `dc -1` to remove the last line.
dc () {
# Prevent the specified history line from being
# saved.
local HISTORY_IGNORE="${(b)$(fc -ln $1 $1)}"
# Write out the history to file, excluding lines that
# match `$HISTORY_IGNORE`.
fc -W
# Dispose of the current history and read the new
# history from file.
fc -p $HISTFILE $HISTSIZE $SAVEHIST
# TA-DA!
print "Deleted '$HISTORY_IGNORE' from history."
}
If you want to additionally prevent all dc commands from being written to history, add the following in your ~/.zshrc file:
zshaddhistory() {
[[ $1 != 'dc '* ]]
}
Update
I've now published a more comprehensive solution as a plugin: https://github.com/marlonrichert/zsh-hist
tldr:
vi $HISTFILE
more details:
run vi $HISTFILE
SHIFT + g — to go to the end
dd — to remove line
:wq — to save and exit
reload session or open a new tab to see changes
remove your line
In BASH [Not ZSH]:
1- in bash terminal type
hsitory # This will list all commands in history .bash_history file with line numbers
ex:
...
987 cd
988 ssh x#127.0.0.1
990 exit
991 cd
2- pick the CMD line number you want to delete
history -d 988
Note: if you want to delete for example last 3 CMDs, just pick the third line number from bottom ex: 988 and repeat the CMD history -d 988 3 times in sequence.
Source

Can the Unix list command 'ls' output numerical chmod permissions?

Is it possible when listing a directory to view numerical Unix permissions such as 644, rather than the symbolic output -rw-rw-r-- ?
Thanks.
it almost can ..
ls -l | awk '{k=0;for(i=0;i<=8;i++)k+=((substr($1,i+2,1)~/[rwx]/) \
*2^(8-i));if(k)printf("%0o ",k);print}'
Closest I can think of (keeping it simple enough) is stat, assuming you know which files you're looking for. If you don't, * can find most of them:
/usr/bin$ stat -c '%a %n' *
755 [
755 a2p
755 a2ps
755 aclocal
...
It handles sticky, suid and company out of the box:
$ stat -c '%a %n' /tmp /usr/bin/sudo
1777 /tmp
4755 /usr/bin/sudo
you can just use GNU find.
find . -printf "%m:%f\n"
You can use the following command
stat -c "%a %n" *
Also you can use any filename or directoryname instead of * to get a specific result.
On Mac, you can use
stat -f '%A %N' *
Use this to display the Unix numerical permission values (octal values) and file name.
stat -c '%a %n' *
Use this to display the Unix numerical permission values (octal values) and the folder's sgid and sticky bit, user name of the owner, group name, total size in bytes and file name.
stat -c '%a %A %U %G %s %n' *
Add %y if you need time of last modification in human-readable format. For more options see stat.
Better version using an Alias
Using an alias is a more efficient way to accomplish what you need and it also includes color. The following displays your results organized by group directories first, display in color, print sizes in human readable format (e.g., 1K 234M 2G) edit your ~/.bashrc and add an alias for your account or globally by editing /etc/profile.d/custom.sh
Typing cls displays your new LS command results.
alias cls="ls -lha --color=always -F --group-directories-first |awk '{k=0;s=0;for(i=0;i<=8;i++){;k+=((substr(\$1,i+2,1)~/[rwxst]/)*2^(8-i));};j=4;for(i=4;i<=10;i+=3){;s+=((substr(\$1,i,1)~/[stST]/)*j);j/=2;};if(k){;printf(\"%0o%0o \",s,k);};print;}'"
Folder Tree
While you are editing your bashrc or custom.sh include the following alias to see a graphical representation where typing lstree will display your current folder tree structure
alias lstree="ls -R | grep ":$" | sed -e 's/:$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'"
It would display:
|-scripts
|--mod_cache_disk
|--mod_cache_d
|---logs
|-run_win
|-scripts.tar.gz
#The MYYN
wow, nice awk! But what about suid, sgid and sticky bit?
You have to extend your filter with s and t, otherwise they will not count and you get the wrong result. To calculate the octal number for this special flags, the procedure is the same but the index is at 4 7 and 10. the possible flags for files with execute bit set are ---s--s--t amd for files with no execute bit set are ---S--S--T
ls -l | awk '{
k = 0
s = 0
for( i = 0; i <= 8; i++ )
{
k += ( ( substr( $1, i+2, 1 ) ~ /[rwxst]/ ) * 2 ^( 8 - i ) )
}
j = 4
for( i = 4; i <= 10; i += 3 )
{
s += ( ( substr( $1, i, 1 ) ~ /[stST]/ ) * j )
j/=2
}
if ( k )
{
printf( "%0o%0o ", s, k )
}
print
}'
For test:
touch blah
chmod 7444 blah
will result in:
7444 -r-Sr-Sr-T 1 cheko cheko 0 2009-12-05 01:03 blah
and
touch blah
chmod 7555 blah
will give:
7555 -r-sr-sr-t 1 cheko cheko 0 2009-12-05 01:03 blah
You don't use ls to get a file's permission information. You use the stat command. It will give you the numerical values you want. The "Unix Way" says that you should invent your own script using ls (or 'echo *') and stat and whatever else you like to give the information in the format you desire.
Building off of the chosen answer and the suggestion to use an alias, I converted it to a function so that passing a directory to list is possible.
# ls, with chmod-like permissions and more.
# #param $1 The directory to ls
function lls {
LLS_PATH=$1
ls -AHl $LLS_PATH | awk "{k=0;for(i=0;i<=8;i++)k+=((substr(\$1,i+2,1)~/[rwx]/) \
*2^(8-i));if(k)printf(\"%0o \",k);print}"
}
Solution
It is strange that still nobody mentioned the (quote) "modern replacement for ls" - an alternative and quite powerful tool exa.
You can easily achieve the desired output by using exa command along with the -l (which is equivalent to ls's -l) and the --octal-permissions options.
Example
Here is a simple example of listing the contents of a user's root directory (/) on a macOS machine using exa command and the --octal-permissions option:
exa -lh --octal-permissions /
Result:
Notice how besides the nice colorful output exa can also show the headers for each column thanks to the -h option (long form is --header).
Read man exa or the official online documentation for more information about how to customize the desired output according to your specific needs.
Considering the question specifies UNIX, not Linux, use of a stat binary is not necessary. The solution below works on a very old UNIX, though a shell other than sh (i.e. bash) was necessary. It is a derivation of glenn jackman's perl stat solution. It seems like an alternative worth exploring for conciseness.
$ alias lls='llsfn () { while test $# -gt 0; do perl -s -e \
'\''#fields = stat "$f"; printf "%04o\t", $fields[2] & 07777'\'' \
-- -f=$1; ls -ld $1; shift; done; unset -f llsf; }; llsfn'
$ lls /tmp /etc/resolv.conf
1777 drwxrwxrwt 7 sys sys 246272 Nov 5 15:10 /tmp
0644 -rw-r--r-- 1 bin bin 74 Sep 20 23:48 /etc/resolv.conf
The alias was developed using information in this answer
The whole answer is a modified version of a solution in this answer
After reading MANY answers here, following links provided in comments back to the original UNIX way of doing this, and while wanting to combined what was offered here, as well as the tips and tricks I've already learned, I came up with a new solution.
First off, I used to use this alias, to give me column headers:
alias l='echo "Dir Size|Perms|Link Count|Owner|Group|Size|Mod. Time|Name"; ls -haFl --time-style=long-iso --color=always --group-directories-first --format=long'
After combining this, with AWK, I first learned I had to alter the awk command, when using the "-s" option for ls, as this shows size in the first column, and you need to then read and parse the second (no longer first) column of data.
The ISSUE I found, was when you then provide input to " l " i.e., you are at a path, and type: l, fine, but what if you type "l" then a directory name? what if you want to list out everything in a subdirectory? This wasn't able to happen, as the alias did not handle input. I was able to handle this with a function. Then AWk broke, which I was able to handle with a sub-alias.
Combining the two worked perfectly.
Added to my .bashrc file
function _bestLS() {
echo 'MODE|Dir Size|Perms|Link Count|Owner|Group|Size|Mod. Time|Name';
if [ "$*" == '' ]; then
alias _awk4ls="awk '{k=0;s=0;for(i=0;i<=8;i++){;k+=((substr(\$2,i+2,1)~/[rwxst]/)*2^(8-i));};j=4;for(i=4;i<=10;i+=3){;s+=((substr(\$1,i,1)~/[stST]/)*j);j/=2;};if(k){;printf(\"%0o%0o \",s,k);};print;}'";
output=`ls -shaFl --time-style=long-iso --color=always -F --group-directories-first --format=long | _awk4ls`;
else
output=`ls -shaFl --time-style=long-iso --color=always -F --group-directories-first --format=long $* | _awk4ls`;
fi
echo "$output"
}
alias l="_bestLS"
Observe (please note, it is a lower case letter L, not a numeric 1, which look the same in this site's font):
>>l
>>l [SOME DIRECTORY]
P.S. Please excuse my very long (3 line) prompt (PS1)

Resources