/dev/fd/ socket or pipe links fail, NOT missing /dev/fd link - bitbake

This single line works fine from a command line shell:
echo hello | /bin/bash -c 'x() { ls -l $1 >&2; strace -f tee $1 ; } &>/tmp/err ; x >( sleep 2)'
but fails if run as part of a bitbake recipe, the attempt to open the substituted /dev/fd/63 fails.
/dev/fd is properly a symlink to /proc/self/fd under bitbake
Under a normal environment, strace shows this behaviour from tee as it tries to open /dev/fd/63
...
openat(AT_FDCWD, "/dev/fd/62", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
...
But under bitbake it is a lot more complicated:
rt_sigprocmask(SIG_BLOCK, [HUP USR1 USR2 ALRM TERM CHLD], [], 8) = 0
lstat("/dev", {st_mode=S_IFDIR|0755, st_size=4040, ...}) = 0
lstat("/dev/fd", {st_mode=S_IFLNK|0777, st_size=13, ...}) = 0
rt_sigprocmask(SIG_BLOCK, [HUP USR1 USR2 ALRM TERM CHLD], [HUP USR1 USR2 ALRM TERM CHLD], 8) = 0
readlink("/dev/fd", "/proc/self/fd", 4096) = 13
rt_sigprocmask(SIG_SETMASK, [HUP USR1 USR2 ALRM TERM CHLD], NULL, 8) = 0
lstat("/proc", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
lstat("/proc/self", {st_mode=S_IFLNK|0777, st_size=0, ...}) = 0
rt_sigprocmask(SIG_BLOCK, [HUP USR1 USR2 ALRM TERM CHLD], [HUP USR1 USR2 ALRM TERM CHLD], 8) = 0
readlink("/proc/self", "11481", 4096) = 5
rt_sigprocmask(SIG_SETMASK, [HUP USR1 USR2 ALRM TERM CHLD], NULL, 8) = 0
lstat("/proc/11481", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
lstat("/proc/11481/fd", {st_mode=S_IFDIR|0500, st_size=0, ...}) = 0
lstat("/proc/11481/fd/63", {st_mode=S_IFLNK|0300, st_size=64, ...}) = 0
rt_sigprocmask(SIG_BLOCK, [HUP USR1 USR2 ALRM TERM CHLD], [HUP USR1 USR2 ALRM TERM CHLD], 8) = 0
readlink("/proc/11481/fd/63", "pipe:[21000117]", 4096) = 15
rt_sigprocmask(SIG_SETMASK, [HUP USR1 USR2 ALRM TERM CHLD], NULL, 8) = 0
lstat("/proc/11481/fd/pipe:[21000117]", 0x7ffce055eef0) = -1 ENOENT (No such file or directory)
stat("/proc/11481/fd/pipe:[21000117]", 0x7ffce055efc0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/proc/11481/fd/pipe:[21000117]", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 ENOENT (No such file or directory)
All this readlink stuff is the problem, it can't safely do that on pipe nodes of /dev/fd/*
pseudo has decided to do something different when opening a file; why doesn't it just open it?

This is a bug in psuedo, used by bitbake for it's fakeroot environment.
Reported as pseudo bug: https://bugzilla.yoctoproject.org/show_bug.cgi?id=13483
This simple command works normally but fails under pseudo:
bash -c 'cat <( echo hello )'
It's going to fail until pseudo is fixed, to not dereference "broken" symlinks to the limit when opening files. i.e. doesn't try to open the broken link, but instead the last working link.

Related

Format output of concatenating 2 variables in unix

I am coding a simple shell script that checks the space of the target path and the space utilization per directory on that target path (example, I am checking space of /path1/home, and also checks how all the folders on /path1/home is consuming the total space.) My question is regarding the output it produces, it is not that pleasing to the eye (uneven spacing). See sample output lines below.
SIZE USER_FOLDER DATE_LAST_MODIFIED
83G FOLDER 1 Apr 15 03:45
34G FOLDER 10 Mar 9 05:02
26G FOLDER 11 Mar 29 13:01
8.2G FOLDER 100 Apr 1 09:42
1.8G FOLDER 101 Apr 11 13:50
1.3G FOLDER 110 Feb 16 09:30
I just want the output format to be in line with the header so it will look neat because I will use it as a report. Here is the code I am using for this part.
ls -1 | grep -v "lost+found" |grep -v "email_body.tmp" > $v_path/Users.tmp
for user in `cat $v_path/Users.tmp | grep -v "Users.tmp"`
do
folder_size=`du -sh $user 2>/dev/null` # should be run using a more privileged user so that other folders can be read (2>/dev/null was used to discard error messages i.e. "du: cannot read directory `./marcnad/.gnupg': Permission denied")
folder_date=`ls -ltr | tr -s " " | cut -f6,7,8,9, -d" " | grep -w $user | cut -f1,2,3, -d" "`
folder_size="$folder_size $folder_date"
echo $folder_size >> $v_path/Users_Usage.tmp
done
echo "Summary of $v_path Disk Space Utilization per folder." >> email_body.tmp
echo "" >> email_body.tmp
echo "SIZE USER_FOLDER DATE_LAST_MODIFIED" >> email_body.tmp
for i in T G M K
do
cat $v_path/Users_Usage.tmp | grep [0-9]$i | sort -nr -k 1 >> $v_path/email_body.tmp
done
Thanks!
EDIT: Formatting
When you print the data use printf instead of echo
cat $v_path/Users_Usage.tmp | while read a b c d e f
do
printf '%-5s%-7%s%-4s%-4s%-3s-6s' $a $b $c $d $e $f
done
See here

start-stop-daemon and javaFX program

this is driving me crazy, please could you help with the start-stop-daemon to start a javafx jar file, where I need to issue the following command to start it
sudo /opt/jdk1.8.0/bin/java -Djavafx.platform=eglfb -cp /opt/jdk1.8.0/jre/lib/jfxrt.jar:/home/pi/prayertime/JavaFXApplication4.jar javafxapplication4.JavaFXApplication4 &
and the start_stop_daemon script is as follows
#!/bin/sh
#
# init script for ship-it
#
### BEGIN INIT INFO
# Provides: ship-it
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: init script for the ship-it box
# Description: We'll have to fill this out later...
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NAME=prayertime
DAEMON=/home/pi/prayertime/JavaFXApplication4.jar
DAEMONARGS="javafxapplication4.JavaFXApplication4"
PIDFILE=/var/run/$NAME.pid
LOGFILE=/var/log/$NAME.log
. /lib/lsb/init-functions
test -f $DAEMON || exit 0
case "$1" in
start)
start-stop-daemon --start --background \
--pidfile $PIDFILE --make-pidfile --startas /bin/bash \
-- -c "exec sudo /opt/jdk1.8.0/bin/java -Djavafx.platform=eglfb -cp /opt/jdk1.8.0/jre/lib/jfxrt.jar: $DAEMON $DAEMONARGS > $LOGFILE 2>&1"
log_end_msg $?
;;

What is difference between tail -f and tailf in unix?

I try to show tail of a text file. If file is small, there is no difference. However if file is too big (~5 gB), tailf does not respond. On the other hand tail -f works fine. What is difference between them?
I have faced the same issue. The log file was about 47GB. The tailf just waits almost infinite. But the tail -f begin to print the output within seconds.
I have digged deeper by examining the underlying system calls using strace command. The results given below:
# strace tailf /var/log/messages
(truncated)
stat("/var/log/messages", {st_mode=S_IFREG|0600, st_size=47432599401, ...}) = 0
open("/var/log/messages", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0600, st_size=47432600425, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7dba2d1000
read(3, "Nov 1 03:23:01 hostnameXXXX"..., 4096) = 4096
read(3, "0.31.148.12)\nNov 1 03:54:33 del"..., 4096) = 4096
read(3, "io.c(600) [receiver=3.0.6]\nNov "..., 4096) = 4096
(truncated)
As you can see, the tailf is trying to read (buffer) all the lines from beginning before generating output to the screen.
Check the output of tail -f below, here it is using the system call lseek (C/C++) to directly jump to end of file and start reading from there:
# strace tail -f /var/log/messages
(truncated)
open("/var/log/messages", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0600, st_size=47294167448, ...}) = 0
lseek(3, 0, SEEK_CUR) = 0
lseek(3, 0, SEEK_END) = 47294170917
lseek(3, 47294169088, SEEK_SET) = 47294169088
(truncated)
From the man page:
tailf will print out the last 10 lines of a file and then wait for the
file to grow. It is similar to tail -f but does not access the file
when it is not growing. This has the side effect of not updating the
access time for the file, so a filesystem flush does not occur periodi-
cally when no log activity is happening.
http://linuxcommand.org/man_pages/tailf1.html
If it doesn't access the file directly it will have some difficulties with very lage files, depending on your machines setup.

Can I use modified date and time as suffix in rsync?

I'm using rsync to synchronize folders, and make backups of existing files if they have been modified. Currently all modified files are backed to a separate directory, with the synchronization time as suffix. This is with the following command:
rsync --times --backup --backup-dir=OldVersions --suffix=`date +'.%y%m%d%H%M'` /SourceDir /DestDir
Now what I would like to do is use the modified date and time of each file that has to be backed up, instead of the time of the synchronization. Any ideas how I would be able to achieve this?
The approach below may work - it was tested on a Linux system.
Just run another script against the OldVersion diretories, after adding a suffix _ZZZZ to the end of the file name so that find can select it. Rename the file ending using the modify timestamp of the file.
Script to rename the files using their modification timestamp. rename.sh
#!/usr/bin/env bash
name=$1
# get file modification time, substitute space with underscore, and remove -,:
modtime=`stat $1 | grep Modify | cut -d ' ' -f 2,3 | sed -e 's/\ /_/g' -e 's/[-:]//g' `
#echo $modtime
newname=`echo $1 | sed -e "s/[[:digit:]]\{10\}_ZZZZ$/$modtime/g"`
#echo $newname
mv $1 $newname
Added the rsync options -r to recurse into directories and -i to show some information.
user1#debian10 /home/user1/test > rsync --backup-dir=OldVersions --suffix=`date +'.%y%m%d%H%M_ZZZZ'` -btri src dest
cd+++++++++ src/
>f+++++++++ src/azvltfexishlm.txt
>f+++++++++ src/dhatkfztklgcan.txt
>f+++++++++ src/feftafvfrdepwezl.txt
>f+++++++++ src/fwclodehxlpg.txt
>f+++++++++ src/ijcjftigjqhxhan.txt
>f+++++++++ src/jdlfsxoinuey.txt
>f+++++++++ src/oljmsfjv.txt
>f+++++++++ src/rbrktqqrtjyxyt.txt
>f+++++++++ src/rqheczjqrjulvlia.txt
>f+++++++++ src/ruyeizqrxstu.txt
>f+++++++++ src/ssuwndmrellunqyq.txt
>f+++++++++ src/vaclfgwqfdihmvis.txt
Ran again after I appended to the files below.
user1#debian10 /home/user1/test > rsync --backup-dir=OldVersions --suffix=`date +'.%y%m%d%H%M_ZZZZ'` -btri src dest
>f.st...... src/azvltfexishlm.txt
>f.st...... src/fwclodehxlpg.txt
>f.st...... src/ijcjftigjqhxhan.txt
>f.st...... src/ruyeizqrxstu.txt
Ran again after I appended to the files below
user1#debian10 /home/user1/test > rsync --backup-dir=OldVersions --suffix=`date +'.%y%m%d%H%M_ZZZZ'` -btri src dest
>f.st...... src/fwclodehxlpg.txt
>f.st...... src/rbrktqqrtjyxyt.txt
>f.st...... src/rqheczjqrjulvlia.txt
Using find to call the script on the backup files:
user1#debian10 /home/user1/test/dest > find . -name "*_ZZZZ" -print -exec ~/bin/rename.sh {} \;
./OldVersions/src/rqheczjqrjulvlia.txt.2110231948_ZZZZ
./OldVersions/src/azvltfexishlm.txt.2110231945_ZZZZ
./OldVersions/src/rbrktqqrtjyxyt.txt.2110231948_ZZZZ
./OldVersions/src/fwclodehxlpg.txt.2110231948_ZZZZ
./OldVersions/src/ruyeizqrxstu.txt.2110231945_ZZZZ
./OldVersions/src/ijcjftigjqhxhan.txt.2110231945_ZZZZ
./OldVersions/src/fwclodehxlpg.txt.2110231945_ZZZZ
./OldVersions/src/ssuwndmrellunqyq.txt.2110231948_ZZZZ
Listing the renamed files.
user1#debian10 /home/user1/test/dest > ls -lR OldVersions/
OldVersions/:
total 4
drwxr-xr-x 2 user1 user1 4096 Oct 23 20:24 src
OldVersions/src:
total 32
-rw-r--r-- 1 user1 user1 1322 Oct 23 19:42 azvltfexishlm.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 2255 Oct 23 19:42 fwclodehxlpg.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 3084 Oct 23 19:45 fwclodehxlpg.txt.20211023_194506.313540547
-rw-r--r-- 1 user1 user1 1178 Oct 23 19:42 ijcjftigjqhxhan.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 485 Oct 23 19:42 rbrktqqrtjyxyt.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 2283 Oct 23 19:42 rqheczjqrjulvlia.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 1579 Oct 23 19:42 ruyeizqrxstu.txt.20211023_194252.673598165
-rw-r--r-- 1 user1 user1 2091 Oct 23 19:42 ssuwndmrellunqyq.txt.20211023_194252.673598165
After each backup script run, then run the rename script. It should be idempotent.
Test scipt for file creation:
#!/usr/bin/env ruby
class Foo
def initialize()
end
def random_string(len)
pattern = "abcdefghijklmnopqrstuvwxyz"
accum = ""
if len == 0
return ""
end
(0..len-1).each do | i |
index = rand(0..25)
accum << pattern[index]
end
return accum
end
def append_data_to_file(fname)
fout = File.open(fname, "a")
num_lines = rand(5..30)
(1..num_lines).each do | lineno |
rand_string = random_string(rand(24..70))
fout.puts(rand_string)
end
fout.close
end
def create_rand_files(path)
Dir.chdir(path) do
num_files = rand(10..20)
puts "file count #{num_files}"
(1..num_files).each do | i |
name_len = rand(8..16)
rand_name = random_string(name_len) + ".txt"
puts "file #{i} name #{rand_name}"
append_data_to_file(rand_name)
end
end
end
def modify_files(path)
arr = Dir.entries(path).select { |x| x != "." && x != ".."}.shuffle
subsize = arr.length / 3
(1..subsize).each do
fname = arr.pop
puts fname
append_data_to_file(fname)
end
end
end
def main
begin
if ARGV.length != 2
puts "use: #{$0} path cmd"
exit
end
path,cmd = ARGV
b = Foo.new
case cmd
when "create"
b.create_rand_files(path)
when "modify"
b.modify_files(path)
else
puts "unknown command #{cmd}"
end
rescue StandardError => e
p e
p e.backtrace.inspect
end
end
main

Date arithmetic in Unix shell scripts

I need to do date arithmetic in Unix shell scripts that I use to control the execution of third party programs.
I'm using a function to increment a day and another to decrement:
IncrementaDia(){
echo $1 | awk '
BEGIN {
diasDelMes[1] = 31
diasDelMes[2] = 28
diasDelMes[3] = 31
diasDelMes[4] = 30
diasDelMes[5] = 31
diasDelMes[6] = 30
diasDelMes[7] = 31
diasDelMes[8] = 31
diasDelMes[9] = 30
diasDelMes[10] = 31
diasDelMes[11] = 30
diasDelMes[12] = 31
}
{
anio=substr($1,1,4)
mes=substr($1,5,2)
dia=substr($1,7,2)
if((anio % 4 == 0 && anio % 100 != 0) || anio % 400 == 0)
{
diasDelMes[2] = 29;
}
if( dia == diasDelMes[int(mes)] ) {
if( int(mes) == 12 ) {
anio = anio + 1
mes = 1
dia = 1
} else {
mes = mes + 1
dia = 1
}
} else {
dia = dia + 1
}
}
END {
printf("%04d%02d%02d", anio, mes, dia)
}
'
}
if [ $# -eq 1 ]; then
tomorrow=$1
else
today=$(date +"%Y%m%d")
tomorrow=$(IncrementaDia $hoy)
fi
but now I need to do more complex arithmetic.
What it's the best and more compatible way to do this?
Assuming you have GNU date, like so:
date --date='1 days ago' '+%a'
And similar phrases.
Here is an easy way for doing date computations in shell scripting.
meetingDate='12/31/2011' # MM/DD/YYYY Format
reminderDate=`date --date=$meetingDate'-1 day' +'%m/%d/%Y'`
echo $reminderDate
Below are more variations of date computation that can be achieved using date utility.
http://www.cyberciti.biz/tips/linux-unix-get-yesterdays-tomorrows-date.html
http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
This worked for me on RHEL.
I have written a bash script for converting dates expressed in English into conventional
mm/dd/yyyy dates. It is called ComputeDate.
Here are some examples of its use. For brevity I have placed the output of each invocation
on the same line as the invocation, separarted by a colon (:). The quotes shown below are not necessary when running ComputeDate:
$ ComputeDate 'yesterday': 03/19/2010
$ ComputeDate 'yes': 03/19/2010
$ ComputeDate 'today': 03/20/2010
$ ComputeDate 'tod': 03/20/2010
$ ComputeDate 'now': 03/20/2010
$ ComputeDate 'tomorrow': 03/21/2010
$ ComputeDate 'tom': 03/21/2010
$ ComputeDate '10/29/32': 10/29/2032
$ ComputeDate 'October 29': 10/1/2029
$ ComputeDate 'October 29, 2010': 10/29/2010
$ ComputeDate 'this monday': 'this monday' has passed. Did you mean 'next monday?'
$ ComputeDate 'a week after today': 03/27/2010
$ ComputeDate 'this satu': 03/20/2010
$ ComputeDate 'next monday': 03/22/2010
$ ComputeDate 'next thur': 03/25/2010
$ ComputeDate 'mon in 2 weeks': 03/28/2010
$ ComputeDate 'the last day of the month': 03/31/2010
$ ComputeDate 'the last day of feb': 2/28/2010
$ ComputeDate 'the last day of feb 2000': 2/29/2000
$ ComputeDate '1 week from yesterday': 03/26/2010
$ ComputeDate '1 week from today': 03/27/2010
$ ComputeDate '1 week from tomorrow': 03/28/2010
$ ComputeDate '2 weeks from yesterday': 4/2/2010
$ ComputeDate '2 weeks from today': 4/3/2010
$ ComputeDate '2 weeks from tomorrow': 4/4/2010
$ ComputeDate '1 week after the last day of march': 4/7/2010
$ ComputeDate '1 week after next Thursday': 4/1/2010
$ ComputeDate '2 weeks after the last day of march': 4/14/2010
$ ComputeDate '2 weeks after 1 day after the last day of march': 4/15/2010
$ ComputeDate '1 day after the last day of march': 4/1/2010
$ ComputeDate '1 day after 1 day after 1 day after 1 day after today': 03/24/2010
I have included this script as an answer to this problem because it illustrates how
to do date arithmetic via a set of bash functions and these functions may prove useful
for others. It handles leap years and leap centuries correctly:
#! /bin/bash
# ConvertDate -- convert a human-readable date to a MM/DD/YY date
#
# Date ::= Month/Day/Year
# | Month/Day
# | DayOfWeek
# | [this|next] DayOfWeek
# | DayofWeek [of|in] [Number|next] weeks[s]
# | Number [day|week][s] from Date
# | the last day of the month
# | the last day of Month
#
# Month ::= January | February | March | April | May | ... | December
# January ::= jan | january | 1
# February ::= feb | january | 2
# ...
# December ::= dec | december | 12
# Day ::= 1 | 2 | ... | 31
# DayOfWeek ::= today | Sunday | Monday | Tuesday | ... | Saturday
# Sunday ::= sun*
# ...
# Saturday ::= sat*
#
# Number ::= Day | a
#
# Author: Larry Morell
if [ $# = 0 ]; then
printdirections $0
exit
fi
# Request the value of a variable
GetVar () {
Var=$1
echo -n "$Var= [${!Var}]: "
local X
read X
if [ ! -z $X ]; then
eval $Var="$X"
fi
}
IsLeapYear () {
local Year=$1
if [ $[20$Year % 4] -eq 0 ]; then
echo yes
else
echo no
fi
}
# AddToDate -- compute another date within the same year
DayNames=(mon tue wed thu fri sat sun ) # To correspond with 'date' output
Day2Int () {
ErrorFlag=
case $1 in
-e )
ErrorFlag=-e; shift
;;
esac
local dow=$1
n=0
while [ $n -lt 7 -a $dow != "${DayNames[n]}" ]; do
let n++
done
if [ -z "$ErrorFlag" -a $n -eq 7 ]; then
echo Cannot convert $dow to a numeric day of wee
exit
fi
echo $[n+1]
}
Months=(31 28 31 30 31 30 31 31 30 31 30 31)
MonthNames=(jan feb mar apr may jun jul aug sep oct nov dec)
# Returns the month (1-12) from a date, or a month name
Month2Int () {
ErrorFlag=
case $1 in
-e )
ErrorFlag=-e; shift
;;
esac
M=$1
Month=${M%%/*} # Remove /...
case $Month in
[a-z]* )
Month=${Month:0:3}
M=0
while [ $M -lt 12 -a ${MonthNames[M]} != $Month ]; do
let M++
done
let M++
esac
if [ -z "$ErrorFlag" -a $M -gt 12 ]; then
echo "'$Month' Is not a valid month."
exit
fi
echo $M
}
# Retrieve month,day,year from a legal date
GetMonth() {
echo ${1%%/*}
}
GetDay() {
echo $1 | col / 2
}
GetYear() {
echo ${1##*/}
}
AddToDate() {
local Date=$1
local days=$2
local Month=`GetMonth $Date`
local Day=`echo $Date | col / 2` # Day of Date
local Year=`echo $Date | col / 3` # Year of Date
local LeapYear=`IsLeapYear $Year`
if [ $LeapYear = "yes" ]; then
let Months[1]++
fi
Day=$[Day+days]
while [ $Day -gt ${Months[$Month-1]} ]; do
Day=$[Day - ${Months[$Month-1]}]
let Month++
done
echo "$Month/$Day/$Year"
}
# Convert a date to normal form
NormalizeDate () {
Date=`echo "$*" | sed 'sX *X/Xg'`
local Day=`date +%d`
local Month=`date +%m`
local Year=`date +%Y`
#echo Normalizing Date=$Date > /dev/tty
case $Date in
*/*/* )
Month=`echo $Date | col / 1 `
Month=`Month2Int $Month`
Day=`echo $Date | col / 2`
Year=`echo $Date | col / 3`
;;
*/* )
Month=`echo $Date | col / 1 `
Month=`Month2Int $Month`
Day=1
Year=`echo $Date | col / 2 `
;;
[a-z]* ) # Better be a month or day of week
Exp=${Date:0:3}
case $Exp in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec )
Month=$Exp
Month=`Month2Int $Month`
Day=1
#Year stays the same
;;
mon|tue|wed|thu|fri|sat|sun )
# Compute the next such day
local DayOfWeek=`date +%u`
D=`Day2Int $Exp`
if [ $DayOfWeek -le $D ]; then
Date=`AddToDate $Month/$Day/$Year $[D-DayOfWeek]`
else
Date=`AddToDate $Month/$Day/$Year $[7+D-DayOfWeek]`
fi
# Reset Month/Day/Year
Month=`echo $Date | col / 1 `
Day=`echo $Date | col / 2`
Year=`echo $Date | col / 3`
;;
* ) echo "$Exp is not a valid month or day"
exit
;;
esac
;;
* ) echo "$Date is not a valid date"
exit
;;
esac
case $Day in
[0-9]* );; # Day must be numeric
* ) echo "$Date is not a valid date"
exit
;;
esac
[0-9][0-9][0-9][0-9] );; # Year must be 4 digits
[0-9][0-9] )
Year=20$Year
;;
esac
Date=$Month/$Day/$Year
echo $Date
}
# NormalizeDate jan
# NormalizeDate january
# NormalizeDate jan 2009
# NormalizeDate jan 22 1983
# NormalizeDate 1/22
# NormalizeDate 1 22
# NormalizeDate sat
# NormalizeDate sun
# NormalizeDate mon
ComputeExtension () {
local Date=$1; shift
local Month=`GetMonth $Date`
local Day=`echo $Date | col / 2`
local Year=`echo $Date | col / 3`
local ExtensionExp="$*"
case $ExtensionExp in
*w*d* ) # like 5 weeks 3 days or even 5w2d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
weeks=`echo $ExtensionExp | col 1`
days=`echo $ExtensionExp | col 2`
days=$[7*weeks+days]
Due=`AddToDate $Month/$Day/$Year $days`
;;
*d ) # Like 5 days or 5d
ExtensionExp=`echo $ExtensionExp | sed 's/[a-z]/ /g'`
days=$ExtensionExp
Due=`AddToDate $Month/$Day/$Year $days`
;;
* )
Due=$ExtensionExp
;;
esac
echo $Due
}
# Pop -- remove the first element from an array and shift left
Pop () {
Var=$1
eval "unset $Var[0]"
eval "$Var=(\${$Var[*]})"
}
ComputeDate () {
local Date=`NormalizeDate $1`; shift
local Expression=`echo $* | sed 's/^ *a /1 /;s/,/ /' | tr A-Z a-z `
local Exp=(`echo $Expression `)
local Token=$Exp # first one
local Ans=
#echo "Computing date for ${Exp[*]}" > /dev/tty
case $Token in
*/* ) # Regular date
M=`GetMonth $Token`
D=`GetDay $Token`
Y=`GetYear $Token`
if [ -z "$Y" ]; then
Y=$Year
elif [ ${#Y} -eq 2 ]; then
Y=20$Y
fi
Ans="$M/$D/$Y"
;;
yes* )
Ans=`AddToDate $Date -1`
;;
tod*|now )
Ans=$Date
;;
tom* )
Ans=`AddToDate $Date 1`
;;
the )
case $Expression in
*day*after* ) #the day after Date
Pop Exp; # Skip the
Pop Exp; # Skip day
Pop Exp; # Skip after
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date 1`
;;
*last*day*of*th*month|*end*of*th*month )
M=`date +%m`
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Year` = yes ]; then
let Day++
fi
Ans=$Month/$Day/$Year
;;
*last*day*of* )
D=${Expression##*of }
D=`NormalizeDate $D`
M=`GetMonth $D`
Y=`GetYear $D`
# echo M is $M > /dev/tty
Day=${Months[M-1]}
if [ $M -eq 2 -a `IsLeapYear $Y` = yes ]; then
let Day++
fi
Ans=$[M]/$Day/$Y
;;
* )
echo "Unknown expression: " $Expression
exit
;;
esac
;;
next* ) # next DayOfWeek
Pop Exp
dow=`Day2Int $DayOfWeek` # First 3 chars
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
n=$[7-dow+tdow]
Ans=`AddToDate $Date $n`
;;
this* )
Pop Exp
dow=`Day2Int $DayOfWeek`
tdow=`Day2Int ${Exp:0:3}` # First 3 chars
if [ $dow -gt $tdow ]; then
echo "'this $Exp' has passed. Did you mean 'next $Exp?'"
exit
fi
n=$[tdow-dow]
Ans=`AddToDate $Date $n`
;;
[a-z]* ) # DayOfWeek ...
M=${Exp:0:3}
case $M in
jan|feb|mar|apr|may|june|jul|aug|sep|oct|nov|dec )
ND=`NormalizeDate ${Exp[*]}`
Ans=$ND
;;
mon|tue|wed|thu|fri|sat|sun )
dow=`Day2Int $DayOfWeek`
Ans=`NormalizeDate $Exp`
if [ ${#Exp[*]} -gt 1 ]; then # Just a DayOfWeek
#tdow=`GetDay $Exp` # First 3 chars
#if [ $dow -gt $tdow ]; then
#echo "'this $Exp' has passed. Did you mean 'next $Exp'?"
#exit
#fi
#n=$[tdow-dow]
#else # DayOfWeek in a future week
Pop Exp # toss monday
Pop Exp # toss in/off
if [ $Exp = next ]; then
Exp=2
fi
n=$[7*(Exp-1)] # number of weeks
n=$[n+7-dow+tdow]
Ans=`AddToDate $Date $n`
fi
;;
esac
;;
[0-9]* ) # Number weeks [from|after] Date
n=$Exp
Pop Exp;
case $Exp in
w* ) let n=7*n;;
esac
Pop Exp; Pop Exp
#echo Calling ComputeDate $Date ${Exp[*]} > /dev/tty
Date=`ComputeDate $Date ${Exp[*]}` #Recursive call
#echo "New date is " $Date > /dev/tty
Ans=`AddToDate $Date $n`
;;
esac
echo $Ans
}
Year=`date +%Y`
Month=`date +%m`
Day=`date +%d`
DayOfWeek=`date +%a |tr A-Z a-z`
Date="$Month/$Day/$Year"
ComputeDate $Date $*
This script makes extensive use of another script I wrote (called col ... many apologies to those who use the standard col supplied with Linux). This version of
col simplifies extracting columns from the stdin. Thus,
$ echo a b c d e | col 5 3 2
prints
e c b
Here it the col script:
#!/bin/sh
# col -- extract columns from a file
# Usage:
# col [-r] [c] col-1 col-2 ...
# where [c] if supplied defines the field separator
# where each col-i represents a column interpreted according to the presence of -r as follows:
# -r present : counting starts from the right end of the line
# -r absent : counting starts from the left side of the line
Separator=" "
Reverse=false
case "$1" in
-r ) Reverse=true; shift;
;;
[0-9]* )
;;
* )Separator="$1"; shift;
;;
esac
case "$1" in
-r ) Reverse=true; shift;
;;
[0-9]* )
;;
* )Separator="$1"; shift;
;;
esac
# Replace each col-i with $i
Cols=""
for f in $*
do
if [ $Reverse = true ]; then
Cols="$Cols \$(NF-$f+1),"
else
Cols="$Cols \$$f,"
fi
done
Cols=`echo "$Cols" | sed 's/,$//'`
#echo "Using column specifications of $Cols"
awk -F "$Separator" "{print $Cols}"
It also uses printdirections for printing out directions when the script is invoked improperly:
#!/bin/sh
#
# printdirections -- print header lines of a shell script
#
# Usage:
# printdirections path
# where
# path is a *full* path to the shell script in question
# beginning with '/'
#
# To use printdirections, you must include (as comments at the top
# of your shell script) documentation for running the shell script.
if [ $# -eq 0 -o "$*" = "-h" ]; then
printdirections $0
exit
fi
# Delete the command invocation at the top of the file, if any
# Delete from the place where printdirections occurs to the end of the file
# Remove the # comments
# There is a bizarre oddity here.
sed '/#!/d;/.*printdirections/,$d;/ *#/!d;s/# //;s/#//' $1 > /tmp/printdirections.$$
# Count the number of lines
numlines=`wc -l /tmp/printdirections.$$ | awk '{print $1}'`
# Remove the last line
numlines=`expr $numlines - 1`
head -n $numlines /tmp/printdirections.$$
rm /tmp/printdirections.$$
To use this place the three scripts in the files ComputeDate, col, and printdirections, respectively. Place the file in directory named by your PATH, typically, ~/bin. Then make them executable with:
$ chmod a+x ComputeDate col printdirections
Problems? Send me some emaiL: morell AT cs.atu.edu Place ComputeDate in the subject.
For BSD / OS X compatibility, you can also use the date utility with -j and -v to do date math. See the FreeBSD manpage for date. You could combine the previous Linux answers with this answer which might provide you with sufficient compatibility.
On BSD, as Linux, running date will give you the current date:
$ date
Wed 12 Nov 2014 13:36:00 AEDT
Now with BSD's date you can do math with -v, for example listing tomorrow's date (+1d is plus one day):
$ date -v +1d
Thu 13 Nov 2014 13:36:34 AEDT
You can use an existing date as the base, and optionally specify the parse format using strftime, and make sure you use -j so you don't change your system date:
$ date -j -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sat 9 Aug 2014 12:37:14 AEST
And you can use this as the base of date calculations:
$ date -v +1d -f "%a %b %d %H:%M:%S %Y %z" "Sat Aug 09 13:37:14 2014 +1100"
Sun 10 Aug 2014 12:37:14 AEST
Note that -v implies -j.
Multiple adjustments can be provided sequentially:
$ date -v +1m -v -1w
Fri 5 Dec 2014 13:40:07 AEDT
See the manpage for more details.
To do arithmetic with dates on UNIX you get the date as the number seconds since the UNIX epoch, do some calculation, then convert back to your printable date format. The date command should be able to both give you the seconds since the epoch and convert from that number back to a printable date. My local date command does this,
% date -n
1219371462
% date 1219371462
Thu Aug 21 22:17:42 EDT 2008
%
See your local date(1) man page.
To increment a day add 86400 seconds.
Why not write your scripts using a language like perl or python instead which more naturally supports complex date processing? Sure you can do it all in bash, but I think you will also get more consistency across platforms using python for example, so long as you can ensure that perl or python is installed.
I should add that it is quite easy to wire in python and perl scripts into a containing shell script.
date --date='1 days ago' '+%a'
It's not a very compatible solution. It will work only in Linux. At least, it didn't worked in Aix and Solaris.
It works in RHEL:
date --date='1 days ago' '+%Y%m%d'
20080807
I have bumped into this a couple of times. My thoughts are:
Date arithmetic is always a pain
It is a bit easier when using EPOCH date format
date on Linux converts to EPOCH, but not on Solaris
For a portable solution, you need to do one of the following:
Install gnu date on solaris (already
mentioned, needs human interaction
to complete)
Use perl for the date part (most unix installs include
perl, so I would generally assume
that this action does not
require additional work).
A sample script (checks for the age of certain user files to see if the account can be deleted):
#!/usr/local/bin/perl
$today = time();
$user = $ARGV[0];
$command="awk -F: '/$user/ {print \$6}' /etc/passwd";
chomp ($user_dir = `$command`);
if ( -f "$user_dir/.sh_history" ) {
#file_dates = stat("$user_dir/.sh_history");
$sh_file_date = $file_dates[8];
} else {
$sh_file_date = 0;
}
if ( -f "$user_dir/.bash_history" ) {
#file_dates = stat("$user_dir/.bash_history");
$bash_file_date = $file_dates[8];
} else {
$bash_file_date = 0;
}
if ( $sh_file_date > $bash_file_date ) {
$file_date = $sh_file_date;
} else {
$file_date = $bash_file_date;
}
$difference = $today - $file_date;
if ( $difference >= 3888000 ) {
print "User needs to be disabled, 45 days old or older!\n";
exit (1);
} else {
print "OK\n";
exit (0);
}
Looking into it further, I think you can simply use date.
I've tried the following on OpenBSD: I took the date of Feb. 29th 2008 and a random hour (in the form of 080229301535) and added +1 to the day part, like so:
$ date -j 0802301535
Sat Mar 1 15:35:00 EST 2008
As you can see, date formatted the time correctly...
HTH
If you want to continue with awk, then the mktime and strftime functions are useful:
BEGIN { dateinit }
{ newdate=daysadd(OldDate,DaysToAdd)}
# daynum: convert DD-MON-YYYY to day count
#-----------------------------------------
function daynum(date, d,m,y,i,n)
{
y=substr(date,8,4)
m=gmonths[toupper(substr(date,4,3))]
d=substr(date,1,2)
return mktime(y" "m" "d" 12 00 00")
}
#numday: convert day count to DD-MON-YYYY
#-------------------------------------------
function numday(n, y,m,d)
{
m=toupper(substr(strftime("%B",n),1,3))
return strftime("%d-"m"-%Y",n)
}
# daysadd: add (or subtract) days from date (DD-MON-YYYY), return new date (DD-MON-YYYY)
#------------------------------------------
function daysadd(date, days)
{
return numday(daynum(date)+(days*86400))
}
#init variables for date calcs
#-----------------------------------------
function dateinit( x,y,z)
{
# Stuff for date calcs
split("JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12", z)
for (x in z)
{
split(z[x],y,":")
gmonths[y[1]]=y[2]
}
}
The book "Shell Script Recipes: A Problem Solution Approach" (ISBN: 978-1-59059-471-1) by Chris F.A. Johnson has a date functions library that might be helpful. The source code is available at http://apress.com/book/downloadfile/2146 (the date functions are in Chapter08/data-funcs-sh within the tar file).
If the GNU version of date works for you, why don't you grab the source and compile it on AIX and Solaris?
http://www.gnu.org/software/coreutils/
In any case, the source ought to help you get the date arithmetic correct if you are going to write you own code.
As an aside, comments like "that solution is good but surely you can note it's not as good as can be. It seems nobody thought of tinkering with dates when constructing Unix." don't really get us anywhere. I found each one of the suggestions so far to be very useful and on target.
Here are my two pennies worth - a script wrapper making use of date and grep.
Example Usage
> sh ./datecalc.sh "2012-08-04 19:43:00" + 1s
2012-08-04 19:43:00 + 0d0h0m1s
2012-08-04 19:43:01
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1s1m1h1d
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" - 1d2d1h2h1m2m1s2sblahblah
2012-08-04 19:43:00 - 1d1h1m1s
2012-08-03 18:41:59
> sh ./datecalc.sh "2012-08-04 19:43:00" x 1d
Bad operator :-(
> sh ./datecalc.sh "2012-08-04 19:43:00"
Missing arguments :-(
> sh ./datecalc.sh gibberish + 1h
date: invalid date `gibberish'
Invalid date :-(
Script
#!/bin/sh
# Usage:
#
# datecalc "<date>" <operator> <period>
#
# <date> ::= see "man date", section "DATE STRING"
# <operator> ::= + | -
# <period> ::= INTEGER<unit> | INTEGER<unit><period>
# <unit> ::= s | m | h | d
if [ $# -lt 3 ]; then
echo "Missing arguments :-("
exit; fi
date=`eval "date -d \"$1\" +%s"`
if [ -z $date ]; then
echo "Invalid date :-("
exit; fi
if ! ([ $2 == "-" ] || [ $2 == "+" ]); then
echo "Bad operator :-("
exit; fi
op=$2
minute=$[60]
hour=$[$minute*$minute]
day=$[24*$hour]
s=`echo $3 | grep -oe '[0-9]*s' | grep -m 1 -oe '[0-9]*'`
m=`echo $3 | grep -oe '[0-9]*m' | grep -m 1 -oe '[0-9]*'`
h=`echo $3 | grep -oe '[0-9]*h' | grep -m 1 -oe '[0-9]*'`
d=`echo $3 | grep -oe '[0-9]*d' | grep -m 1 -oe '[0-9]*'`
if [ -z $s ]; then s=0; fi
if [ -z $m ]; then m=0; fi
if [ -z $h ]; then h=0; fi
if [ -z $d ]; then d=0; fi
ms=$[$m*$minute]
hs=$[$h*$hour]
ds=$[$d*$day]
sum=$[$s+$ms+$hs+$ds]
out=$[$date$op$sum]
formattedout=`eval "date -d #$out +\"%Y-%m-%d %H:%M:%S\""`
echo $1 $2 $d"d"$h"h"$m"m"$s"s"
echo $formattedout
This works for me:
TZ=GMT+6;
export TZ
mes=`date --date='2 days ago' '+%m'`
dia=`date --date='2 days ago' '+%d'`
anio=`date --date='2 days ago' '+%Y'`
hora=`date --date='2 days ago' '+%H'`

Resources