1. 程式人生 > >crontab環境變數設定

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不能執行相應的指令碼,使用者將會收到一個郵件說明其中的原因。