วัตถุประสงค์ของการใช้ shift ในเชลล์สคริปคืออะไร?


118

ฉันเจอสคริปต์นี้แล้ว:

#! /bin/bash                                                                                                                                                                                           

if (( $# < 3 )); then
  echo "$0 old_string new_string file [file...]"
  exit 0
else
  ostr="$1"; shift
  nstr="$1"; shift  
fi

echo "Replacing \"$ostr\" with \"$nstr\""
for file in $@; do
  if [ -f $file ]; then
    echo "Working with: $file"
    eval "sed 's/"$ostr"/"$nstr"/g' $file" > $file.tmp 
    mv $file.tmp $file
  fi  
done

ความหมายของบรรทัดที่ใช้shiftคืออะไร ฉันคิดว่าสคริปต์ควรใช้กับอาร์กิวเมนต์อย่างน้อย ... ?

คำตอบ:


141

shiftเป็นbashแบบในตัวซึ่งชนิดของการเอาอาร์กิวเมนต์ออกจากจุดเริ่มต้นของรายการอาร์กิวเมนต์ ระบุว่า 3 ข้อโต้แย้งให้กับสคริปต์ที่มีอยู่ใน$1, $2, $3แล้วเรียกร้องให้shift จะทำให้ใหม่$2 จะเปลี่ยนสองทำใหม่เก่า สำหรับข้อมูลเพิ่มเติมดูที่นี่:$1shift 2$1$3


25
+1 หมายเหตุว่ามันไม่หมุนพวกเขากำลังขยับพวกเขาออกจากอาร์เรย์ (จากอาร์กิวเมนต์) Shift / unshift และ pop / push เป็นชื่อสามัญสำหรับการจัดการทั่วไปประเภทนี้ (ดูเช่นทุบตีpushdและpopd)
goldilocks

1
การอ้างอิงที่ชัดเจน: gnu.org/software/bash/manual/bashref.html#index-shift
glenn jackman

@ goldilocks จริงมาก แทนที่จะ "หมุน" ฉันควรหาวิธีใช้คำอื่นดีกว่าเช่น "ย้ายอาร์กิวเมนต์ไปข้างหน้าในตัวแปรอาร์กิวเมนต์" อย่างไรก็ตามฉันหวังว่าตัวอย่างในข้อความและลิงก์ไปยังการอ้างอิงจะทำให้ชัดเจนอย่างไรก็ตาม
humanityANDpeace

แม้ว่าสคริปต์จะกล่าวถึงbashคำถามทั่วไปสำหรับเชลล์ทั้งหมดดังนั้นมาตรฐาน Posix จะมีผลบังคับใช้: pubs.opengroup.org/onlinepubs/9699919799/utilities/ …
calandoa

32

ในฐานะที่เป็นความคิดเห็น Goldilocks' และการอ้างอิงของมนุษยชาติอธิบาย shiftreassigns พารามิเตอร์ตำแหน่ง ( $1, $2ฯลฯ ) เพื่อที่$1จะใช้เวลาอยู่กับค่าเก่า$2, $2ใช้เวลากับค่าของ$3ฯลฯ*   ค่าเก่า$1จะถูกยกเลิก ( $0ไม่เปลี่ยนแปลง) เหตุผลบางประการในการทำเช่นนี้ ได้แก่ :

  • มันช่วยให้คุณเข้าถึงอาร์กิวเมนต์ที่สิบ (ถ้ามี) ได้ง่ายขึ้น  $10ไม่ทำงาน - ถูกตีความว่า$1ต่อกันกับ0 (และอาจสร้างบางอย่างเช่นHello0) หลังจากอาร์กิวเมนต์สิบกลายเป็นshift $9(อย่างไรก็ตามในเชลล์ที่ทันสมัยที่สุดคุณสามารถใช้${10})
  • เมื่อBash Guide for Beginnersแสดงให้เห็นแล้วก็สามารถใช้ในการวนรอบข้อโต้แย้งได้ IMNSHO นี่คือเงอะงะ; forดีกว่ามากสำหรับสิ่งนั้น
  • เช่นเดียวกับในสคริปต์ตัวอย่างของคุณมันทำให้ง่ายต่อการประมวลผลข้อโต้แย้งทั้งหมดด้วยวิธีเดียวกันยกเว้นบางอย่าง ตัวอย่างเช่นในสคริปต์ของคุณ $1และ$2เป็นสตริงข้อความในขณะที่$3และพารามิเตอร์อื่น ๆ ทั้งหมดเป็นชื่อไฟล์

ดังนั้นนี่คือวิธีการเล่น สมมติว่าสคริปต์ของคุณถูกเรียกใช้Patryk_scriptและถูกเรียกว่าเป็น

Patryk_script USSR Russia Treaty1 Atlas2 Pravda3

สคริปต์เห็น

$1 = USSR
$2 = Russia
$3 = Treaty1
$4 = Atlas2
$5 = Pravda3

คำสั่งostr="$1"กำหนดตัวแปรเพื่อostr คำสั่งUSSRแรกshiftเปลี่ยนพารามิเตอร์ตำแหน่งดังนี้:

$1 = Russia
$2 = Treaty1
$3 = Atlas2
$4 = Pravda3

คำสั่งnstr="$1"กำหนดตัวแปรเพื่อnstr คำสั่งRussiaที่สองshiftเปลี่ยนพารามิเตอร์ตำแหน่งดังนี้:

$1 = Treaty1
$2 = Atlas2
$3 = Pravda3

และแล้วforการเปลี่ยนแปลงห่วงUSSR( $ostr) ไปRussia( $nstr) ในไฟล์Treaty1, และAtlas2Pravda3



สคริปต์มีปัญหาเล็กน้อย

  1. สำหรับไฟล์ใน $ @; ทำ

    หากสคริปต์ถูกเรียกใช้เป็น

    Patryk_script สหภาพโซเวียตรัสเซีย Treaty1 "World Atlas2" Pravda3

    มันเห็น

    $ 1 = ล้าหลัง
    $ 2 = รัสเซีย
    $ 3 = Treaty1
    $ 4 = World Atlas2
    $ 5 = Pravda3

    แต่เพราะ$@ไม่ได้ยกพื้นที่ในWorld Atlas2ไม่ได้ยกมาและforห่วงคิดว่ามันมีสี่ไฟล์: Treaty1, World, และAtlas2 Pravda3ควรจะเป็นอย่างนี้

    สำหรับไฟล์ใน "$ @"; ทำ

    (เพื่ออ้างอิงอักขระพิเศษใด ๆ ในอาร์กิวเมนต์) หรือเพียงแค่

    สำหรับไฟล์ทำ

    (ซึ่งเทียบเท่ากับเวอร์ชันที่ยาวกว่า)

  2. eval "sed 's /" $ ostr "/" $ nstr "/ g' $ file"

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

    Patryk_script "'; rm *;'" สนธิสัญญารัสเซีย 1 Atlas2 Pravda3

    มันจะดำเนินการrm *! นี่เป็นเรื่องที่น่ากังวลอย่างมากหากสคริปต์สามารถเรียกใช้ด้วยสิทธิ์ที่สูงกว่าของผู้ใช้ที่เรียกใช้งาน เช่นหากสามารถเรียกใช้ผ่านsudoหรือเรียกใช้จากเว็บอินเตอร์เฟส อาจไม่สำคัญหากคุณใช้เป็นตัวคุณเองในไดเรกทอรีของคุณ แต่มันสามารถเปลี่ยนเป็น

    sed "s / $ ostr / $ nstr / g" "$ file"

    สิ่งนี้ยังคงมีความเสี่ยงอยู่บ้าง แต่ก็มีความรุนแรงน้อยกว่ามาก

  3. if [ -f $file ], > $file.tmpและmv $file.tmp $file ควรจะเป็นif [ -f "$file" ], > "$file.tmp"และmv "$file.tmp" "$file"ตามลำดับในการจัดการชื่อไฟล์ที่อาจมีช่องว่าง (หรือตัวตลกและอื่น ๆ ) ในพวกเขา ( eval "sed …คำสั่งยัง mangles ชื่อไฟล์ที่มีช่องว่างอยู่ด้วย)


* shiftรับอาร์กิวเมนต์ตัวเลือก: จำนวนเต็มบวกที่ระบุจำนวนพารามิเตอร์ที่ต้องการเลื่อน ค่าเริ่มต้นคือหนึ่ง ( 1) ยกตัวอย่างเช่นshift 4เป็นสาเหตุ$5 ที่จะกลายเป็น$1, $6ที่จะกลายเป็น$2และอื่น ๆ (โปรดทราบว่าตัวอย่างในคู่มือ Bash สำหรับผู้เริ่มต้นผิด) และเพื่อให้สคริปต์ของคุณสามารถแก้ไขได้

ostr="$1"
nstr="$2"
shift 2

ซึ่งอาจถือว่ามีความชัดเจนมากขึ้น



หมายเหตุท้าย / คำเตือน:

ภาษาพร้อมรับคำสั่งของ Windows (ไฟล์แบทช์) ยังรองรับSHIFTคำสั่งซึ่งโดยพื้นฐานแล้วเป็นสิ่งเดียวกับshiftคำสั่งใน Unix shells โดยมีความแตกต่างที่โดดเด่นหนึ่งประการซึ่งฉันจะซ่อนเพื่อพยายามป้องกันไม่ให้ผู้อื่นสับสน:

  • คำสั่งเช่นSHIFT 4นั้นเป็นข้อผิดพลาดทำให้เกิดข้อผิดพลาด“ พารามิเตอร์ไม่ถูกต้องกับคำสั่ง SHIFT”
  • SHIFT /nที่nเป็นจำนวนเต็มระหว่าง 0 และ 8 ที่ถูกต้อง - แต่มันไม่ได้เปลี่ยนครั้ง มันเลื่อนหนึ่งครั้งโดยเริ่มจาก อาร์กิวเมนต์ที่n ดังนั้น ทำให้(อาร์กิวเมนต์ที่ห้า) กลายเป็นและอื่น ๆ โดยปล่อยให้อาร์กิวเมนต์ 0 ถึง 3 เพียงอย่างเดียวn SHIFT /4%5%4,  %6%5


2
shiftสามารถนำไปใช้กับwhile, untilและแม้กระทั่งforลูปสำหรับการจัดการอาร์เรย์ในรูปแบบที่ลึกซึ้งยิ่งกว่าการforวนซ้ำแบบธรรมดา ฉันมักจะพบว่ามีประโยชน์ในforลูปเช่น ... set -f -- $args; IFS=$split; for arg do set -- $arg; shift "${number_of_fields_to_discard}" && fn_that_wants_split_arg "$arg"; done
mikeserv

3

คำอธิบายที่ง่ายที่สุดคือสิ่งนี้ พิจารณาคำสั่ง:

/bin/command.sh SET location Cebu
/bin/command.sh SET location Cebu, Philippines 6014 

หากไม่มีความช่วยเหลือจากshiftคุณคุณจะไม่สามารถแยกค่าที่สมบูรณ์ของสถานที่ได้เนื่องจากค่านี้อาจมีความยาวตามอำเภอใจ เมื่อคุณเลื่อนสองครั้ง args SETและlocationถูกลบออกอย่างนั้น:

x="$@"
echo "location = $x"

ใช้เวลาจ้องมอง$@สิ่งนั้นนานมาก นั่นหมายความว่ามันสามารถบันทึกตำแหน่งที่สมบูรณ์ลงในตัวแปรxแม้จะมีช่องว่างและเครื่องหมายจุลภาคที่มี ดังนั้นในการสรุปเราเรียกแล้วต่อมาเราเรียกค่าของสิ่งที่เหลือจากตัวแปรshift$@

อัปเดต ฉันกำลังเพิ่มตัวอย่างสั้น ๆ ด้านล่างที่แสดงแนวคิดและประโยชน์ของการshiftไม่มีข้อมูลซึ่งจะเป็นการยากมากที่จะแยกฟิลด์อย่างถูกต้อง

#!/bin/sh
#

[ $# -eq 0 ] && return 0

a=$1
shift
b=$@
echo $a
[ -n "$b" ] && echo $b

คำอธิบาย : หลังจากนั้นshiftตัวแปรbจะมีส่วนที่เหลือของสิ่งที่ถูกส่งผ่านโดยไม่คำนึงถึงช่องว่างหรืออื่น ๆ การ[ -n "$b" ] && echo $bป้องกันเช่นนี้เราจะพิมพ์bเท่านั้นหากมีเนื้อหา


(1) นี่ไม่ใช่คำอธิบายที่ง่ายที่สุด มันเป็นมุมที่น่าสนใจ (2) แต่ตราบใดที่คุณต้องการที่จะเชื่อมพวงของการขัดแย้ง (หรือทั้งหมดของพวกเขา), คุณอาจต้องการที่จะได้รับเป็นนิสัยของการใช้แทน"$*" "$@"(3) ฉันจะลบคำตอบของคุณ แต่ฉันทำไม่ได้เพราะคุณพูดว่า“ กะสองครั้ง” แต่คุณไม่ได้แสดงเป็นรหัส (4) หากคุณต้องการให้สคริปต์สามารถรับค่าหลายคำจากบรรทัดคำสั่งได้ผู้ใช้ควรใส่ค่าทั้งหมดลงในเครื่องหมายคำพูด … (ต่อ)
สกอตต์

(ต่อ) คุณอาจไม่สนใจวันนี้ แต่วิธีการของคุณไม่อนุญาตให้คุณมีช่องว่างหลายช่อง ( Philippines   6014) ช่องว่างนำหน้าช่องว่างต่อท้ายหรือแท็บ (5) BTW x="${*:3}"คุณยังอาจจะสามารถที่จะทำสิ่งที่คุณทำที่นี่ในทุบตีด้วย
สกอตต์

ฉันพยายามถอดรหัสที่ความคิดเห็น '*** ของคุณไม่อนุญาตให้มีช่องว่างหลายจุด ***' เกิดขึ้นเพราะนี่ไม่เกี่ยวข้องกับshiftคำสั่ง คุณอาจอ้างถึง $ @ เมื่อเทียบกับ $ * ความแตกต่างเล็กน้อย แต่ความจริงก็ยังคงอยู่ตัวอย่างของฉันด้านบนสามารถแยกตำแหน่งที่ตั้งได้อย่างถูกต้องไม่ว่าจะมีช่องว่างจำนวนเท่าใดที่มีส่วนท้ายหรือด้านหน้าไม่สำคัญ หลังจากการเปลี่ยนตำแหน่งจะมีตำแหน่งที่ถูกต้อง
typelogic

2

shift ปฏิบัติต่ออาร์กิวเมนต์บรรทัดคำสั่งเป็นคิว FIFO มันเป็นองค์ประกอบ popleft ทุกครั้งที่มีการเรียก

array = [a, b, c]
shift equivalent to
array.popleft
[b, c]
$1, $2,$3 can be interpreted as index of the array.
$# is the length of array
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.