crontab如果不注意的話早晚會出問題,而且這種問題一旦出一次,就會永遠記得,因為這種問題很折騰人。
——某前輩
設置了一個crontab
30 0 * * * cd /home/work/user/huangbx/research/getfeature/data/current; sh resample.sh &>/dev/null
$sh resample.sh是可以運行的
$head -5 resample.sh
##對事實數據進行采樣
set -x
g_date=`date -d "3 days ago " +%Y%m%d`
可是放到crontab里面就無法運行了。
從網上了解到一般crontab無法運行的問題都是由環境變量在crontab中不一定可識別引起的。可是resample.sh中並沒有涉及環境變量的使用。
經過多番嘗試,終於發現是代碼的第一行的中文注釋引起的問題,添加上#!/bin/sh后就可以運行了。
總結了一下:
crontab中必須十分注意環境變量的使用
#!/bin/sh並不是必須,只是當沒有sha-bang的時候,也不要在第一行有"#"后帶的中文注釋!!
最好當然是加上sha-bang啦 #!/bin/sh
2008-11-3補充:
之前沒有特別注意環境變量引起的crontab失敗,今天果然就遇到了。
問題描述:cron了某sh文件,里面執行多個操作,既調用了外部的shell腳本,也調用了外部的python腳本。從運行日志看,發現部分腳本被調用,而部分python腳本沒有被調用。沒有被調用的均是python腳本,而且均使用了MySQLdb模塊(第三方模塊)。
該腳本在平時直接使用sh命令均可以正常執行。
出錯信息:
Traceback (most recent call last):
File "areafile.py", line 2, in <module>
import MySQLdb
File "build/bdist.linux-x86_64/egg/MySQLdb/__init__.py", line 19, in <module>
File "build/bdist.linux-x86_64/egg/_mysql.py", line 7, in <module>
File "build/bdist.linux-x86_64/egg/_mysql.py", line 6, in __bootstrap__
ImportError: libmysqlclient.so.15: cannot open shared object file: No such file or directory
MySQLdb需要調用mysql這個庫,可是系統並不知道你的mysql安裝在哪里 : (
問題解決:
在總控的shell腳本中添加一句話
export LD_LIBRARY_PATH=/home/work/local/mysql5/lib/mysql
(也就是來自~/.bash_profile中的LD_LIBRARY_PATH字段)后程序終於可以在crontab中正常啟動。
解釋:
1) ~/.bash_profile && ~/.bashrc
用戶登陸Linux操作系統的時候,"/etc/profile", "~/.bash_profile"等配置文件會被自動執行。執行過程是這樣的:登陸Linux系統時,首先啟動"/etc/profile",然后啟動用戶目錄下的"~/.bash_profile",如果"~/.bash_login"和"~/.profile"文件存在的時候也會在執行"~/.bash_profile"后被依次調用。
下面看看"~/.bash_profile"文件里面有什么東西
$cat ~/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin:/home/work/local/python/lib/python2.5/site-packages/django/bin/:$HOME/bin:/home/work/local/mysql5/bin/;
LD_LIBRARY_PATH=/home/work/local/mysql5/lib/mysql
alias py='/home/work/local/python/bin/python'
export PATH LD_LIBRARY_PATH
unset USERNAME
可以看到~/.bash_profile文件先調用~/.bashrc,然后再把PATH和LD_LIBRARY_PATH加載。
.bash_profile和.bashrc的差別
/etc/profile:此文件為系統的每個用戶設置環境信息,當用戶第一次登錄時,該文件被執行.
並從/etc/profile.d目錄的設置文件中搜集shell的設置.
/etc/bashrc:為每一個運行bash shell的用戶執行此文件.當bash shell被打開時,該文件被讀取.
~/.bash_profile:每個用戶都可使用該文件輸入專用於自己使用的shell信息,當用戶登錄時,該
文件僅僅執行一次!默認情況下,他設置一些環境變量,執行用戶的.bashrc文件.
~/.bashrc:該文件包含專用於你的bash shell的bash信息,當登錄時及每次打開新的shell時,該
該文件被讀取.
~/.bash_logout:當每次退出系統(退出bash shell)時,執行該文件.
/etc/profile是全局性的功能,其中設置的變量作用於所有用戶,~/.bash_profile中設置的變量能繼承/etc/profile中的變量並作用於用戶。
~/.bash_profile 是交互式、login 方式進入 bash 運行的
~/.bashrc 是交互式 non-login 方式進入 bash 運行的
通常二者設置大致相同,所以通常前者會調用后者。(http://blog.chinaunix.net/u2/63775/showart_527708.html )
可是在運行crontab的時候,是non_login方式調用程序的,此時~/.bash_profile並不會被提前調用。所以,crontab的運行環境相對於login方式進入bash運行的環境來說小得多。如果程序涉及~/.bash_profile使用的環境變量,那么,部分在login方式可以正常運行的程序在crontab下就無法運行。
在我的程序中,系統無法識別MySQLdb,於是解決方案就是在總控的shell腳本中添加這樣一句:
export LD_LIBRARY_PATH=/home/work/local/mysql5/lib/mysql
更加推薦的解決方案:
在cron中加入
LD_LIBRARY_PATH=/home/work/local/mysql5/lib/mysql
這樣cron中所有使用mysql的東東都可以順利運行了 : ) 而且這樣可以使得操作更加清晰。
終極推薦解決方案:
30 12 * * * source ~/.bashrc && cd /home/work/mydir && ./myproj
2) LD_LIBRARY_PATH
Linux運行時有一套共享庫(*.so)。共享庫的尋找和加載是通過/lib/ld.so (RunTime Shared Library Loader)完成的。ld.so在標准路徑(/lib, /usr/lib)下尋找共享庫。可是如果第三方庫並非安裝在標准路徑下,程序運行的時候就會出現無法找到庫的錯誤,類似於下面這個報錯
ld.so.1: curl: fatal: libgcc_s.so.1: open failed: No such file or directory
通過設置環境變量LD_LIBRARY_PATH可以讓ld.so尋找非標准路徑的共享庫。LD_LIBRARY_PATH中可以設置多個路徑,路徑之間通過冒號":"分割。LD_LIBRARY_PATH中的路徑先於標准路徑的查找。
在~/.bash_profile中添加如下代碼(比如把mysql的so文件添加進LD_LIBRARY_PATH)
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/work/local/mysql5/lib/mysql
export LD_LIBRARY_PATH
由於~/.bash_profile在用戶登陸時會加載(而且僅加載)一次,然后ld.so就會在標准路徑和LD_LIBRARY_PATH中自動尋找和加載共享庫。
LD_LIBRARY_PATH的缺點:(參考http://xahlee.org/UnixResource_dir/_/ldpath.html)
"For security reasons, LD_LIBRARY_PATH is ignored at runtime for executables that have their setuid or setgid bit set. This severely limits the usefulness of LD_LIBRARY_PATH." ... .... ....."LD_LIBRARY_PATH is one of those insidious things that once it gets set globally for a user, things tend to happen which cause people to rely on it being set. Eventually when LD_LIBRARY_PATH needs to be changed or removed, mass breakage will occur!" ... ... ......"Nowadays you specify the run-time path for an executable at link stage with the -R (or sometimes -rpath) flag to ld. There's also LD_RUN_PATH which is an environment variable which acts to ld just like specifying -R. Before all this you had only -L, which applied not only during compile-time, but during run time as well. There was no way to say “use this directory during compile time” but “use this other directory at run time”. There were some rather spectacular failure modes that one could get in to because of this. "
文中同時給出了如何合理使用LD_LIBRARY_PATH:(雖然沒有完全看懂,還是貼一下,期待不久的將來能看懂)
1) Never ever set LD_LIBRARY_PATH globally.
If you must ship binaries that use shared libraries and want to allow your clients to install the program outside a 'standard' location, do one of the following:
Ship your binaries as .o files, and as part of the install process relink them with the correct installation library path.
Ship executables with a very long “dummy” run-time library path, and as part of the install process use a binary editor to substitute the correct install library path in the executable.
2) If you are forced to set LD_LIBRARY_PATH, do so only as part of a wrapper.
3). Remove the link-time aspect of LD_LIBRARY_PATH.....It would be much cleaner if LD_LIBRARY_PATH only had influence at run-time. If necessary, invent some other environment variable for the job (LD_LINK_PATH).
3) ld.so.conf
除了設置LD_LIBRARY_PATH外,還可以設置/etc/ld.so.conf。然后運行ldconfig生成ld.so.cache。ld.so查找公共庫的時候也會從ld.so.cache中查找。
不過http://xahlee.org/UnixResource_dir/_/ldpath.html還是猛烈批判了ld.so.conf的設置。
"Some OS's (e.g. Linux) have a configurable loader. You can configure what run-time paths to look in by modifying /etc/ld.so.conf. This is almost as bad a LD_LIBRARY_PATH! Install scripts should never modify this file! This file should contain only the standard library locations as shipped with the OS. "
LD_LIBRARY_PATH的runtime Linker詳細行為可以參考http://docs.sun.com/app/docs/doc/819-0690/chapter6-63352?a=view
轉自:http://hi.baidu.com/huangboxiang/blog/item/f798a7dc3eb096e877c63833.html
大家都知道crontab是個好東東,可以定時執行一些任務,幫助你監控系統狀況,幫助你每天重復的做一些機械的事情。但是crontab有一個壞毛病,就是它總是不會缺省的從用戶profile文件中讀取環境變量參數,經常導致在手工執行某個腳本時是成功的,但是到crontab中試圖讓它定期執行時就是會出錯
原先我用一個很傻的辦法,就是在腳本中直接指定所有的環境變量參數,每次寫腳本都要寫好多好多PATH啦,LD_LIBRARY_PATH之類的環境變量參數
后來發現其實可以直接在腳本里先執行一下用戶的profile文件,就OK了
如果是Linux環境下的腳本,腳本的頭上用缺省的#!/bin/sh就可以了,如果是Solaris環境下的腳本,腳本頭上用#!/bin/ksh
然后第一個部分先寫這些:
###################
. /etc/profile
. ~/.bash_profile
##################
這樣,crontab在執行腳本的時候,就能夠讀到用戶的環境變量參數啦。。。一點兒小技巧而已 ^_^
附:
如果你是在cron里提交的,請注意:
不要假定c r o n知道所需要的特殊環境,它其實並不知道。所以你要保證在s h e l l腳本中提供所有必要的路徑和環境變量,除了一些自動設置的全局變量。
如果c r o n不能運行相應的腳本,用戶將會收到一個郵件說明其中的原因。