Làm thế nào để so sánh hai ngày trong một vỏ?


88

Làm thế nào có thể so sánh hai ngày trong một vỏ?

Dưới đây là một ví dụ về cách tôi muốn sử dụng cái này, mặc dù nó không hoạt động như hiện tại:

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

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

Làm thế nào tôi có thể đạt được kết quả mong muốn?


Bạn muốn lặp để làm gì? Bạn có nghĩa là có điều kiện hơn là vòng lặp?
Mat

Tại sao bạn gắn thẻ các tập tin ? Là những ngày thực sự tập tin thời gian?
thao tác

Kiểm tra điều này trên stackoverflow: stackoverflow.com/questions/8116503/ từ
slm

Câu trả lời:


107

Câu trả lời đúng vẫn còn thiếu:

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

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

Lưu ý rằng điều này đòi hỏi ngày GNU. dateCú pháp tương đương cho BSD date(như được tìm thấy trong OSX theo mặc định) làdate -j -f "%F" 2014-08-19 +"%s"


10
Donno tại sao ai đó đánh giá thấp điều này, nó khá chính xác và không giải quyết được việc nối các chuỗi xấu xí. Chuyển đổi ngày của bạn thành dấu thời gian unix (tính bằng giây), so sánh dấu thời gian unix.
Nicholi

Tôi nghĩ, ưu điểm chính của giải pháp này là bạn có thể thực hiện các phép cộng hoặc phép trừ một cách dễ dàng. Ví dụ: tôi muốn có thời gian chờ là x giây, nhưng ý tưởng đầu tiên của tôi ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) rõ ràng là sai.
iGEL

Bạn có thể bao gồm độ chính xác của nano giây vớidate -d 2013-07-18 +%s%N
typelogic

32

Bạn đang thiếu định dạng ngày để so sánh:

#!/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

Bạn đang thiếu cấu trúc vòng lặp, làm thế nào bạn có kế hoạch để có được nhiều ngày hơn?


Điều này không hoạt động với datevận chuyển với macOS.
Flimm

date -dlà không chuẩn và không hoạt động trên một UNIX điển hình. Trên BSD, nó thậm chí còn cố gắng đặt giá trị kenel DST.
schily

32

Có thể sử dụng so sánh chuỗi tiêu chuẩn để so sánh thứ tự thời gian [của các chuỗi trong định dạng năm, tháng, ngày].

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

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

Rất may, khi [các chuỗi sử dụng định dạng YYYY-MM-DD] được sắp xếp * theo thứ tự bảng chữ cái, chúng cũng được sắp xếp * theo thứ tự thời gian.

(* - được sắp xếp hoặc so sánh)

Không có gì lạ mắt cần thiết trong trường hợp này. vâng


Đâu là chi nhánh khác ở đây?
Vladimir Despotovic

Đây là giải pháp duy nhất hiệu quả với tôi trên Sierra MacOS
Vladimir Despotovic

Điều này đơn giản hơn là giải pháp của tôi về if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];định dạng Ngày này được thiết kế để được sắp xếp bằng cách sử dụng sắp xếp số alpha tiêu chuẩn hoặc -để được loại bỏ và sắp xếp theo số.
ctrl-alt-delor

Đây là câu trả lời thông minh nhất ở đây
Ed Randall

7

Đây không phải là vấn đề về cấu trúc vòng lặp mà là các kiểu dữ liệu.

Những ngày đó ( todatecond) là các chuỗi, không phải số, vì vậy bạn không thể sử dụng toán tử "-ge" của test. (Hãy nhớ rằng ký hiệu dấu ngoặc vuông tương đương với lệnh test.)

Những gì bạn có thể làm là sử dụng một ký hiệu khác nhau cho ngày của bạn để chúng là số nguyên. Ví dụ:

date +%Y%m%d

sẽ tạo ra một số nguyên như 20130715 cho ngày 15 tháng 7 năm 2013. Sau đó, bạn có thể so sánh ngày của mình với "-ge" và các toán tử tương đương.

Cập nhật: nếu ngày của bạn được đưa ra (ví dụ: bạn đang đọc chúng từ một tệp ở định dạng 2013-07-13) thì bạn có thể xử lý trước chúng một cách dễ dàng với tr.

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

6

Toán tử -gechỉ hoạt động với số nguyên mà ngày của bạn không có.

Nếu tập lệnh của bạn là tập lệnh bash hoặc ksh hoặc zsh, bạn có thể sử dụng <toán tử thay thế. Toán tử này không có sẵn trong dấu gạch ngang hoặc các vỏ khác không vượt quá tiêu chuẩn POSIX.

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

Trong bất kỳ shell nào, bạn có thể chuyển đổi chuỗi thành số trong khi vẫn tuân theo thứ tự ngày chỉ bằng cách xóa dấu gạch ngang.

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

Ngoài ra, bạn có thể đi truyền thống và sử dụng các exprtiện ích.

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

Vì việc gọi các quy trình con trong một vòng lặp có thể chậm, bạn có thể thực hiện chuyển đổi bằng cách sử dụng các cấu trúc xử lý chuỗi shell.

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

Tất nhiên, nếu bạn có thể truy xuất ngày mà không có dấu gạch nối ở vị trí đầu tiên, bạn sẽ có thể so sánh chúng với -ge.


Đâu là chi nhánh khác trong bash này?
Vladimir Despotovic

2
Đây dường như là câu trả lời đúng nhất. Rất hữu ích!
Mojtaba Rezaeian

1

Tôi chuyển đổi Chuỗi thành dấu thời gian unix (giây kể từ 1.1.1970 0: 0: 0). Đây có thể so sánh dễ dàng

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

Điều này không hoạt động với các datetàu có macOS.
Flimm

Có vẻ giống như unix.stackexchange.com/a/170982/100394 đã được đăng một thời gian trước của bạn
roaima

1

Ngoài ra còn có phương pháp này từ bài viết có tiêu đề: Tính toán ngày và thời gian đơn giản trong BASH từ unix.com.

Các chức năng này là một đoạn trích từ một kịch bản trong chuỗi đó!

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))
}

Sử dụng

# 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"

Điều này không hoạt động với các datetàu có macOS.
Flimm

Nếu bạn cài đặt gdate trên MacOS thông qua brew, điều này có thể dễ dàng sửa đổi để hoạt động bằng cách thay đổi datethành gdate.
slm

1
Câu trả lời này rất hữu ích trong trường hợp của tôi. Nó nên đánh giá lên!
Mojtaba Rezaeian

1

câu trả lời cụ thể

Vì tôi muốn giảm dĩa và phép rất nhiều mánh khóe, nên có mục đích của tôi:

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

Chà, bây giờ:

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

Điều này sẽ điền lại cả hai biến $todate$cond, chỉ sử dụng một ngã ba, với ouptut của date -f -wich mất stdio để đọc một ngày theo dòng.

Cuối cùng, bạn có thể phá vỡ vòng lặp của mình với

((todate>=cond))&&break

Hoặc như một chức năng :

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
}

Sử dụng công cụ dựng sẵn của printf có thể hiển thị thời gian ngày bằng giây từ epoch (xem man bash;-)

Kịch bản này chỉ sử dụng một ngã ba.

Thay thế với dĩa giới hạn và chức năng đọc ngày

Điều này sẽ tạo ra một quy trình con chuyên dụng (chỉ một ngã ba):

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

Khi đầu vào và đầu ra được mở, mục nhập fifo có thể bị xóa.

Chức năng:

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

Nota Để ngăn chặn các nhánh vô dụng như thế todate=$(myDate 2013-07-18), biến phải được đặt bởi chính hàm. Và để cho phép cú pháp miễn phí (có hoặc không có dấu ngoặc kép để xác thực), tên biến phải là đối số cuối cùng .

Sau đó so sánh ngày:

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

có thể kết xuất:

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

nếu bên ngoài một vòng lặp.

Hoặc sử dụng chức năng bash shell-Connector:

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

hoặc là

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

(Không hoàn toàn giống nhau: .shkhông chứa tập lệnh kiểm tra đầy đủ nếu không có nguồn gốc)

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
}

Hồ sơ bash chứa một minh chứng tốt cho việc sử dụng date -f -có thể làm giảm yêu cầu nguồn tài nguyên.
F. Hauri

0

ngày là chuỗi, không phải là số nguyên; bạn không thể so sánh chúng với các toán tử số học tiêu chuẩn.

một cách tiếp cận bạn có thể sử dụng, nếu dấu phân cách được đảm bảo là -:

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

Điều này hoạt động càng xa càng tốt bash 2.05b.0(1)-release.


0

Bạn cũng có thể sử dụng hàm dựng sẵn của mysql để so sánh ngày. Nó cho kết quả sau 'ngày'.

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

Chỉ cần chèn các giá trị $DBUSER$DBPASSWORDbiến theo của bạn.


0

FWIW: trên Mac, có vẻ như chỉ cung cấp một ngày như "2017-05-05" vào ngày sẽ nối thời gian hiện tại với ngày và bạn sẽ nhận được giá trị khác nhau mỗi khi bạn chuyển đổi thành thời gian kỷ nguyên. để có được thời gian kỷ nguyên nhất quán hơn cho các ngày cố định, bao gồm các giá trị giả cho giờ, phút và giây:

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

Đây có thể là một số lẻ giới hạn trong phiên bản ngày được gửi cùng với macOS .... được thử nghiệm trên macOS 10.12.6.


0

Echoing câu trả lời @Gilles ("Toán tử -ge chỉ hoạt động với số nguyên"), đây là một ví dụ.

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

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

datetimechuyển đổi số nguyên sang epoch, mỗi câu trả lời của @ mike-q tại https://stackoverflow.com/questions/10990949/convert-date-time-opes-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"lớn hơn (gần đây hơn) "$old_date", nhưng biểu thức này đánh giá không chính xác là False:

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

... được hiển thị rõ ràng, ở đây:

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

So sánh số nguyên:

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

Lý do tại sao phép so sánh này không hoạt động tốt với chuỗi ngày được gạch nối là shell giả sử một số có số 0 đứng đầu là một số bát phân, trong trường hợp này là tháng "07". Đã có nhiều giải pháp được đề xuất nhưng nhanh nhất và dễ nhất là loại bỏ các dấu gạch nối. Bash có một tính năng thay thế chuỗi giúp cho việc này nhanh chóng và dễ dàng sau đó việc so sánh có thể được thực hiện dưới dạng biểu thức số học:

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.