จะเปรียบเทียบสองวันในเชลล์ได้อย่างไร?


88

จะเปรียบเทียบสองวันในเชลล์ได้อย่างไร

นี่คือตัวอย่างของวิธีที่ฉันต้องการใช้ถึงแม้ว่ามันจะไม่ทำงานตามที่เป็นอยู่:

todate=2013-07-18
cond=2013-07-15

if [ $todate -ge $cond ];
then
    break
fi                           

ฉันจะบรรลุผลลัพธ์ที่ต้องการได้อย่างไร


คุณต้องการห่วงอะไร คุณหมายถึงเงื่อนไขมากกว่าวงหรือไม่
จ้า

ทำไมคุณแท็กไฟล์ ? วันที่เหล่านั้นเป็นไฟล์จริงหรือไม่?
จัดการ

ลองใช้งานได้ที่ stackoverflow: stackoverflow.com/questions/8116503/…
slm

คำตอบ:


107

คำตอบที่ถูกต้องยังคงหายไป:

todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)

if [ $todate -ge $cond ];
then
    break
fi  

โปรดทราบว่านี่ต้องใช้วันที่ของ GNU dateไวยากรณ์ที่เทียบเท่าสำหรับ BSD date(เหมือนที่พบใน OSX โดยค่าเริ่มต้น) คือdate -j -f "%F" 2014-08-19 +"%s"


10
ดอนโนว่าทำไมบางคนถึงไม่ลงคะแนนสิ่งนี้มันค่อนข้างแม่นยำและไม่เกี่ยวข้องกับการเรียงสตริงที่น่าเกลียด แปลงวันที่ของคุณเป็น Unix Timestamp (เป็นวินาที), เปรียบเทียบ Unix Timestamps
Nicholi

ฉันคิดว่าฉันได้เปรียบหลักของวิธีนี้คือคุณสามารถเพิ่มหรือลบได้อย่างง่ายดาย ตัวอย่างเช่นฉันต้องการหมดเวลา x วินาที แต่ความคิดแรกของฉัน ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) ผิดอย่างเห็นได้ชัด
iGEL

คุณสามารถรวมความแม่นยำของนาโนวินาทีด้วยdate -d 2013-07-18 +%s%N
typelogic

32

คุณไม่มีรูปแบบวันที่สำหรับการเปรียบเทียบ:

#!/bin/bash

todate=$(date -d 2013-07-18 +"%Y%m%d")  # = 20130718
cond=$(date -d 2013-07-15 +"%Y%m%d")    # = 20130715

if [ $todate -ge $cond ]; #put the loop where you need it
then
 echo 'yes';
fi

คุณกำลังขาดโครงสร้างวนลูปเหมือนกันคุณวางแผนที่จะรับวันที่เพิ่มขึ้นอย่างไร


สิ่งนี้ไม่สามารถใช้ได้dateกับ macOS ที่จัดส่งมา
Flimm

date -dไม่ใช่มาตรฐานและไม่ทำงานบน UNIX ทั่วไป เมื่อวันที่ BSD DSTก็ยังพยายามที่จะกำหนดค่า
schily

32

สามารถใช้การเปรียบเทียบสตริงมาตรฐานเพื่อเปรียบเทียบลำดับการจัดเรียง [ของสตริงในปีเดือนรูปแบบวัน]

date_a=2013-07-18
date_b=2013-07-15

if [[ "$date_a" > "$date_b" ]] ;
then
    echo "break"
fi

โชคดีที่เมื่อ [สตริงที่ใช้รูปแบบ YYYY-MM-DD] ถูกเรียงลำดับ * ตามลำดับตัวอักษรพวกเขาจะเรียงลำดับ * ตามลำดับเวลาด้วย

(* - เรียงลำดับหรือเปรียบเทียบ)

ไม่ต้องการแฟนซีในกรณีนี้ เย้!


สาขาอื่นอยู่ที่นี่ที่ไหน
Vladimir Despotovic

นี่เป็นทางออกเดียวที่ทำงานกับฉันใน Sierra MacOS
Vladimir Despotovic

นี่เป็นวิธีที่ง่ายกว่าที่โซลูชันของฉันของif [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... รูปแบบวันที่นี้ได้รับการออกแบบเพื่อจัดเรียงโดยใช้การเรียงลำดับตัวเลขอัลฟ่าและตัวเลขหรือ-ถูกแยกและเรียงลำดับตัวเลข
ctrl-alt-delor

นี่เป็นคำตอบที่ฉลาดที่สุดที่นี่
Ed Randall

7

นี่ไม่ใช่ปัญหาของโครงสร้างวนรอบ แต่เป็นชนิดข้อมูล

วันเหล่านั้น ( todateและcond) เป็นสตริงตัวเลขไม่ได้ดังนั้นคุณจึงไม่สามารถใช้ "-GE" testการดำเนินการของ (โปรดจำไว้ว่าสัญกรณ์วงเล็บเหลี่ยมเทียบเท่ากับคำสั่งtest)

สิ่งที่คุณสามารถทำได้คือใช้สัญลักษณ์ที่แตกต่างกันสำหรับวันที่ของคุณเพื่อให้พวกเขาเป็นจำนวนเต็ม ตัวอย่างเช่น:

date +%Y%m%d

จะสร้างจำนวนเต็มเช่น 20130715 ในวันที่ 15 กรกฎาคม 2013 จากนั้นคุณสามารถเปรียบเทียบวันที่ของคุณกับ "-ge" และตัวดำเนินการเทียบเท่า

อัปเดต:หากได้รับวันที่ของคุณ (เช่นคุณกำลังอ่านจากไฟล์ในรูปแบบ 2013-07-13) คุณสามารถประมวลผลล่วงหน้าได้อย่างง่ายดายด้วย tr

$ echo "2013-07-15" | tr -d "-"
20130715

6

โอเปอเรเตอร์-geใช้งานได้เฉพาะกับจำนวนเต็มซึ่งวันที่ของคุณไม่

หากสคริปต์ของคุณเป็น bash หรือ ksh หรือ zsh script คุณสามารถใช้<โอเปอเรเตอร์แทน ตัวดำเนินการนี้ไม่พร้อมใช้งานในเส้นประหรือกระสุนอื่น ๆ ที่ไม่ได้เกินมาตรฐาน POSIX

if [[ $cond < $todate ]]; then break; fi

ในเชลล์ใด ๆ คุณสามารถแปลงสตริงเป็นตัวเลขในขณะที่เคารพลำดับวันที่โดยการลบเครื่องหมายขีดคั่น

if [ "$(echo "$todate" | tr -d -)" -ge "$(echo "$cond" | tr -d -)" ]; then break; fi

หรือคุณสามารถไปที่ดั้งเดิมและใช้exprยูทิลิตี้

if expr "$todate" ">=" "$cond" > /dev/null; then break; fi

เนื่องจากการเรียกใช้กระบวนการย่อยในลูปอาจช้าคุณอาจต้องการทำการแปลงสภาพโดยใช้การประมวลผลสตริงของเชลล์

todate_num=${todate%%-*}${todate#*-}; todate_num=${todate_num%%-*}${todate_num#*-}
cond_num=${cond%%-*}${cond#*-}; cond_num=${cond_num%%-*}${cond_num#*-}
if [ "$todate_num" -ge "$cond_num" ]; then break; fi

แน่นอนถ้าคุณสามารถเรียกคืนวันที่โดยไม่มีเครื่องหมายยัติภังค์ในตอนแรกคุณจะสามารถเปรียบเทียบ-geได้


สาขาอื่นใน bash นี้อยู่ที่ไหน
Vladimir Despotovic

2
นี่ดูเหมือนจะเป็นคำตอบที่ถูกต้องที่สุด มีประโยชน์มาก!
Mojtaba Rezaeian

1

ฉันแปลง Strings ให้เป็น unix-timestamps (วินาทีตั้งแต่ 1.1.1970 0: 0: 0) สิ่งเหล่านี้สามารถเปรียบเทียบได้ง่าย

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi

สิ่งนี้ไม่สามารถใช้งานได้dateกับ macOS
Flimm

ดูเหมือนว่าจะเหมือนกันกับunix.stackexchange.com/a/170982/100397ซึ่งโพสต์ก่อนหน้าคุณ
roaima

1

นอกจากนี้ยังมีวิธีการนี้จากบทความชื่อ: การคำนวณวันที่และเวลาอย่างง่ายใน BASHจาก unix.com

ฟังก์ชั่นเหล่านี้เป็นข้อความที่ตัดตอนมาจากสคริปต์ในชุดข้อความนั้น!

date2stamp () {
    date --utc --date "$1" +%s
}

dateDiff (){
    case $1 in
        -s)   sec=1;      shift;;
        -m)   sec=60;     shift;;
        -h)   sec=3600;   shift;;
        -d)   sec=86400;  shift;;
        *)    sec=86400;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2-dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec/sec*abs))
}

การใช้

# calculate the number of days between 2 dates
    # -s in sec. | -m in min. | -h in hours  | -d in days (default)
    dateDiff -s "2006-10-01" "2006-10-31"
    dateDiff -m "2006-10-01" "2006-10-31"
    dateDiff -h "2006-10-01" "2006-10-31"
    dateDiff -d "2006-10-01" "2006-10-31"
    dateDiff  "2006-10-01" "2006-10-31"

วิธีนี้ใช้ไม่ได้dateกับ macOS
Flimm

หากคุณติดตั้ง gdate บน MacOS ผ่านชงนี้สามารถแก้ไขได้ง่ายในการทำงานโดยการเปลี่ยนไปdate gdate
slm

1
คำตอบนี้มีประโยชน์มากในกรณีของฉัน ควรให้คะแนน!
Mojtaba Rezaeian

1

คำตอบเฉพาะ

เนื่องจากฉันชอบลดส้อมและอนุญาตให้ใช้กลอุบายได้มากมายมีจุดประสงค์ของฉัน:

todate=2013-07-18
cond=2013-07-15

ตอนนี้ดี:

{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")

สิ่งนี้จะเติมข้อมูลทั้งสองตัวแปรอีกครั้ง$todateและ$condใช้ส้อมเดียวเท่านั้นโดยมี ouptut ซึ่งdate -f -ใช้stdioเพื่ออ่านวันที่ทีละบรรทัด

ในที่สุดคุณสามารถทำลายวงของคุณด้วย

((todate>=cond))&&break

หรือเป็นฟังก์ชั่น :

myfunc() {
    local todate cond
    { read todate
      read cond
    } < <(
      date -f - +%s <<<"$1"$'\n'"$2"
    )
    ((todate>=cond))&&return
    printf "%(%a %d %b %Y)T older than %(%a %d %b %Y)T...\n" $todate $cond
}

การใช้ของ builtin ซึ่งprintfสามารถแสดงเวลาวันที่ด้วยวินาทีจากยุค (ดูman bash;-)

สคริปต์นี้ใช้ส้อมเดียวเท่านั้น

ทางเลือกที่มีส้อม จำกัด และฟังก์ชั่นเครื่องอ่านวันที่

สิ่งนี้จะสร้างกระบวนการย่อยเฉพาะ (หนึ่งส้อมเท่านั้น):

mkfifo /tmp/fifo
exec 99> >(exec stdbuf -i 0 -o 0 date -f - +%s >/tmp/fifo 2>&1)
exec 98</tmp/fifo
rm /tmp/fifo

เมื่ออินพุตและเอาต์พุตเปิดอยู่ฟีเจอร์ห้ารายการอาจถูกลบได้

ฟังก์ชั่น:

myDate() {
    local var="${@:$#}"
    shift
    echo >&99 "${@:1:$#-1}"
    read -t .01 -u 98 $var
}

Notaเพื่อป้องกันการยกส้อมที่ไร้ประโยชน์เช่นtodate=$(myDate 2013-07-18)นั้นตัวแปรจะถูกกำหนดโดยฟังก์ชั่นของตัวเอง และเพื่ออนุญาตให้ใช้ไวยากรณ์ฟรี (โดยมีหรือไม่มีเครื่องหมายอัญประกาศเพื่อถอดรหัส) ชื่อตัวแปรจะต้องเป็นอาร์กิวเมนต์สุดท้าย

จากนั้นเปรียบเทียบวันที่:

myDate 2013-07-18        todate
myDate Mon Jul 15 2013   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

อาจแสดงผล:

To: Thu Jul 18 00:00:00 2013 > Cond: Mon Jul 15 00:00:00 2013
bash: break: only meaningful in a `for', `while', or `until' loop

ถ้าอยู่นอกวง

หรือใช้ฟังก์ชั่นทุบตีเปลือกเชื่อมต่อ:

wget https://github.com/F-Hauri/Connector-bash/raw/master/shell_connector.bash

หรือ

wget https://f-hauri.ch/vrac/shell_connector.sh

(ไม่เหมือนกันทุกประการ: .shมีสคริปต์ทดสอบแบบเต็มหากไม่ได้มา)

source shell_connector.sh
newConnector /bin/date '-f - +%s' @0 0

myDate 2013-07-18        todate
myDate "Mon Jul 15 2013"   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

การทำโปรไฟล์ทุบตีมีการสาธิตที่ดีสำหรับวิธีการใช้date -f -สามารถลดความต้องการทรัพยากร ressources
F. Hauri

0

วันที่เป็นสตริงไม่ใช่จำนวนเต็ม; คุณไม่สามารถเปรียบเทียบกับตัวดำเนินการทางคณิตศาสตร์มาตรฐานได้

วิธีการหนึ่งที่คุณสามารถใช้หากมีการรับรองตัวแยก-:

IFS=-
read -ra todate <<<"$todate"
read -ra cond <<<"$cond"
for ((idx=0;idx<=numfields;idx++)); do
  (( todate[idx] > cond[idx] )) && break
done
unset IFS

bash 2.05b.0(1)-releaseนี้ทำงานไกลกลับเป็น


0

คุณยังสามารถใช้ฟังก์ชัน builtin ของ mysql เพื่อเปรียบเทียบวันที่ มันให้ผลลัพธ์เป็น 'วัน'

from_date="2015-01-02"
date_today="2015-03-10"
diff=$(mysql -u${DBUSER} -p${DBPASSWORD} -N -e"SELECT DATEDIFF('${date_today}','${from_date}');")

เพียงแค่ใส่ค่า$DBUSERและ$DBPASSWORDตัวแปรตามที่คุณต้องการ


0

FWIW: สำหรับ Mac ดูเหมือนว่าการป้อนวันที่อย่างเช่น "2017-05-05" จะเป็นการต่อท้ายเวลาปัจจุบันกับวันที่และคุณจะได้รับค่าที่แตกต่างกันในแต่ละครั้งที่คุณแปลงเป็นเวลายุค เพื่อให้ได้เวลายุคที่สอดคล้องกันมากขึ้นสำหรับวันที่กำหนดไว้รวมถึงค่าตัวอย่างสำหรับชั่วโมงนาทีและวินาที:

date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"

นี่อาจเป็นเรื่องแปลกที่ จำกัด เฉพาะวันที่ส่งมาพร้อมกับ macOS .... ทดสอบบน macOS 10.12.6


0

Echoing @Gilles answer ("โอเปอเรเตอร์ -ge ใช้ได้กับจำนวนเต็มเท่านั้น") นี่คือตัวอย่าง

curr_date=$(date +'%Y-%m-%d')
echo "$curr_date"
2019-06-20

old_date="2019-06-19"
echo "$old_date"
2019-06-19

datetimeการแปลงจำนวนเต็มเป็นepochต่อคำตอบของ @ mike-q ที่https://stackoverflow.com/questions/10990949/convert-date-time-string-to-epoch-in-bash :

curr_date_int=$(date -d "${curr_date}" +"%s")
echo "$curr_date_int"
1561014000

old_date_int=$(date -d "${old_date}" +"%s")
echo "$old_date_int"
1560927600

"$curr_date"มากกว่า (มากกว่าล่าสุด) "$old_date"แต่นิพจน์นี้ประเมินอย่างไม่ถูกต้องเป็นFalse:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; fi

... แสดงอย่างชัดเจนที่นี่:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; else echo 'bar'; fi
bar

การเปรียบเทียบจำนวนเต็ม:

if [[ "$curr_date_int" -ge "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -gt "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; fi

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; else echo 'bar'; fi
bar

0

เหตุผลที่การเปรียบเทียบนี้ใช้งานไม่ได้กับสตริงวันที่ที่มียัติภังค์คือเชลล์จะถือว่าตัวเลขที่มี 0 นำหน้าคือฐานแปดในกรณีนี้คือเดือน "07" มีวิธีแก้ไขปัญหาต่าง ๆ ที่เสนอ แต่เร็วที่สุดและง่ายที่สุดคือการตัดออกยัติภังค์ Bash มีคุณสมบัติการแทนที่สตริงที่ทำให้ง่ายและรวดเร็วจากนั้นการเปรียบเทียบสามารถดำเนินการเป็นนิพจน์ทางคณิตศาสตร์:

todate=2013-07-18
cond=2013-07-15

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.